From b27425f30685b58ba8ac02c7268a144881323da5 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 6 Mar 2018 11:03:34 +0100 Subject: [PATCH 001/401] 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/401] 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/401] 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/401] 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 252d19071495d678ded05841ed492b23613f67f1 Mon Sep 17 00:00:00 2001 From: Nick Driver Date: Fri, 30 Mar 2018 14:27:37 -0400 Subject: [PATCH 005/401] fix missing comma fix ip-dst and vulnerability input --- misp_modules/modules/expansion/xforceexchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/xforceexchange.py b/misp_modules/modules/expansion/xforceexchange.py index 459c69a..d027f99 100644 --- a/misp_modules/modules/expansion/xforceexchange.py +++ b/misp_modules/modules/expansion/xforceexchange.py @@ -14,7 +14,7 @@ extensions = {"ip1": "ipr/%s", sys.path.append('./') misperrors = {'error': 'Error'} -mispattributes = {'input': ['ip-src','ip-dst' 'vulnerability', 'md5', 'sha1', 'sha256'], +mispattributes = {'input': ['ip-src', 'ip-dst', 'vulnerability', 'md5', 'sha1', 'sha256'], 'output': ['ip-src', 'ip-dst', 'text', 'domain']} # possible module-types: 'expansion', 'hover' or both From 370011c0817f8f773eed0f1406c2fea85ca94bf5 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Wed, 2 May 2018 12:43:34 +0200 Subject: [PATCH 006/401] threatanalyzer_import - fix regkey issue --- misp_modules/modules/import_mod/threatanalyzer_import.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index fded508..fd16246 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -457,8 +457,6 @@ def cleanup_regkey(item): r'\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\Bag', r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\\' } - item = item.replace('\\REGISTRY\\MACHINE\\', 'HKLM\\') - item = item.replace('\\REGISTRY\\USER\\', 'HKCU\\') if list_in_string(noise_substrings, item, regex=True): return None return item From 27a22e5d86ecb1b7c2f81cfa1f02401464270cd1 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Thu, 3 May 2018 09:42:38 +0200 Subject: [PATCH 007/401] threatanalyzer_import - loads sample info + pollution fix --- .../import_mod/threatanalyzer_import.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index fd16246..da01b93 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -62,12 +62,12 @@ def handler(q=False): if re.match(r"Analysis/proc_\d+/modified_files/.+\.", zip_file_name) and "mapping.log" not in zip_file_name: sample_md5 = zip_file_name.split('/')[-1].split('.')[0] if sample_md5 in modified_files_mapping: - sample_filename = modified_files_mapping[sample_md5] - # print("{} maps to {}".format(sample_md5, sample_filename)) + current_sample_filename = modified_files_mapping[sample_md5] + # print("{} maps to {}".format(sample_md5, current_sample_filename)) with zf.open(zip_file_name, mode='r', pwd=None) as fp: file_data = fp.read() results.append({ - 'values': sample_filename, + 'values': current_sample_filename, 'data': base64.b64encode(file_data).decode(), 'type': 'malware-sample', 'categories': ['Artifacts dropped', 'Payload delivery'], 'to_ids': True, 'comment': ''}) @@ -76,8 +76,18 @@ def handler(q=False): file_data = fp.read() analysis_json = json.loads(file_data.decode('utf-8')) results += process_analysis_json(analysis_json) - # if 'sample' in zip_file_name: - # sample['data'] = base64.b64encode(file_data).decode() + try: + sample_filename = analysis_json.get('analysis').get('@filename') + if sample_filename: + with zf.open('sample', mode='r', pwd=None) as fp: + file_data = fp.read() + results.append({ + 'values': sample_filename, + 'data': base64.b64encode(file_data).decode(), + 'type': 'malware-sample', 'categories': ['Artifacts dropped', 'Payload delivery'], 'to_ids': True, 'comment': ''}) + except Exception as e: + # no 'sample' in archive, might be an url analysis, just ignore + pass else: try: @@ -455,7 +465,9 @@ def cleanup_regkey(item): r'\\Software\\Classes\\CLSID\\', r'\\Software\\Classes\\Local Settings\\MuiCache\\', r'\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\Bag', - r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\\' + r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\\', + r'\\Software\\Microsoft\\Tracing\\powershell_RASMANCS\\', + r'\\Software\\Microsoft\\Tracing\\powershell_RASAPI32\\' } if list_in_string(noise_substrings, item, regex=True): return None From 7b4db1ce5ce99491a47b2cdb006a5bf80542e676 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Tue, 15 May 2018 12:59:55 +0200 Subject: [PATCH 008/401] threatanalyzer_import - minor generic noise removal --- .../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 da01b93..326de89 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.6', 'author': 'Christophe Vandeplas', +moduleinfo = {'version': '0.7', 'author': 'Christophe Vandeplas', 'description': 'Import for ThreatAnalyzer archive.zip/analysis.json files', 'module-type': ['import']} @@ -451,23 +451,24 @@ def cleanup_filepath(item): def cleanup_regkey(item): noise_substrings = { - r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\', - r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\', + r'\\CurrentVersion\\Explorer\\FileExts\\[a-z\.]+\\OpenWith', r'\\CurrentVersion\\Explorer\\RecentDocs\\', r'\\CurrentVersion\\Explorer\\UserAssist\\', - r'\\CurrentVersion\\Explorer\\FileExts\\[a-z\.]+\\OpenWith', - r'\\Software\\Microsoft\\Internet Explorer\\Main\\WindowsSearch', - r'\\Software\\Microsoft\\Office\\[0-9\.]+\\', - r'\\SOFTWARE\\Microsoft\\OfficeSoftwareProtectionPlatform\\', - r'\\Software\\Microsoft\\Office\\Common\\Smart Tag\\', - r'\\Usage\\SpellingAndGrammarFiles', - r'^HKLM\\Software\\Microsoft\\Tracing\\', + r'\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\Bag', r'\\Software\\Classes\\CLSID\\', r'\\Software\\Classes\\Local Settings\\MuiCache\\', - r'\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\Bag', - r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\\', + r'\\Software\\Microsoft\\Internet Explorer\\Main\\WindowsSearch', + r'\\Software\\Microsoft\\Office\\[0-9\.]+\\', + r'\\Software\\Microsoft\\Office\\Common\\Smart Tag\\', + r'\\SOFTWARE\\Microsoft\\OfficeSoftwareProtectionPlatform\\', + r'\\Software\\Microsoft\\Shared Tools\\Panose\\', + r'\\Software\\Microsoft\\Tracing\\', + r'\\Software\\Microsoft\\Tracing\\powershell_RASAPI32\\', r'\\Software\\Microsoft\\Tracing\\powershell_RASMANCS\\', - r'\\Software\\Microsoft\\Tracing\\powershell_RASAPI32\\' + r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\\', + r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\', + r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\', + r'\\Usage\\SpellingAndGrammarFiles' } if list_in_string(noise_substrings, item, regex=True): return None From 67cecc89d0ea492cd7f77fd42b4f91a9814e339a Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Tue, 15 May 2018 12:59:55 +0200 Subject: [PATCH 009/401] threatanalyzer_import - minor generic noise removal --- .../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 da01b93..326de89 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.6', 'author': 'Christophe Vandeplas', +moduleinfo = {'version': '0.7', 'author': 'Christophe Vandeplas', 'description': 'Import for ThreatAnalyzer archive.zip/analysis.json files', 'module-type': ['import']} @@ -451,23 +451,24 @@ def cleanup_filepath(item): def cleanup_regkey(item): noise_substrings = { - r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\', - r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\', + r'\\CurrentVersion\\Explorer\\FileExts\\[a-z\.]+\\OpenWith', r'\\CurrentVersion\\Explorer\\RecentDocs\\', r'\\CurrentVersion\\Explorer\\UserAssist\\', - r'\\CurrentVersion\\Explorer\\FileExts\\[a-z\.]+\\OpenWith', - r'\\Software\\Microsoft\\Internet Explorer\\Main\\WindowsSearch', - r'\\Software\\Microsoft\\Office\\[0-9\.]+\\', - r'\\SOFTWARE\\Microsoft\\OfficeSoftwareProtectionPlatform\\', - r'\\Software\\Microsoft\\Office\\Common\\Smart Tag\\', - r'\\Usage\\SpellingAndGrammarFiles', - r'^HKLM\\Software\\Microsoft\\Tracing\\', + r'\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\Bag', r'\\Software\\Classes\\CLSID\\', r'\\Software\\Classes\\Local Settings\\MuiCache\\', - r'\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\Bag', - r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\\', + r'\\Software\\Microsoft\\Internet Explorer\\Main\\WindowsSearch', + r'\\Software\\Microsoft\\Office\\[0-9\.]+\\', + r'\\Software\\Microsoft\\Office\\Common\\Smart Tag\\', + r'\\SOFTWARE\\Microsoft\\OfficeSoftwareProtectionPlatform\\', + r'\\Software\\Microsoft\\Shared Tools\\Panose\\', + r'\\Software\\Microsoft\\Tracing\\', + r'\\Software\\Microsoft\\Tracing\\powershell_RASAPI32\\', r'\\Software\\Microsoft\\Tracing\\powershell_RASMANCS\\', - r'\\Software\\Microsoft\\Tracing\\powershell_RASAPI32\\' + r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\\', + r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\', + r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\', + r'\\Usage\\SpellingAndGrammarFiles' } if list_in_string(noise_substrings, item, regex=True): return None From 0593dbb40807b01988b88902e8732e150538e6e2 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Wed, 16 May 2018 11:50:47 +0200 Subject: [PATCH 010/401] ta import - more filter for pollution --- .../import_mod/threatanalyzer_import.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index 326de89..757f849 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -421,20 +421,22 @@ def cleanup_url(item): def cleanup_filepath(item): noise_substrings = { - 'C:\\Windows\\Prefetch\\', - '\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\', - '\\AppData\\Roaming\\Microsoft\\Office\\Recent\\', - 'C:\\ProgramData\\Microsoft\\OfficeSoftwareProtectionPlatform\\Cache\\cache.dat', - '\\AppData\\Local\\Microsoft\\Windows\\Temporary Internet Files\\Content.', - '\\AppData\\Local\\Microsoft\\Internet Explorer\\Recovery\\High\\', + '\\AppData\\Local\\GDIPFONTCACHEV1.DAT', '\\AppData\\Local\\Microsoft\\Internet Explorer\\DOMStore\\', - '\\AppData\\LocalLow\\Microsoft\\Internet Explorer\\Services\\search_', - '\\AppData\\Local\\Microsoft\\Windows\\History\\History.', - '\\AppData\\Roaming\\Microsoft\\Windows\\Cookies\\', - '\\AppData\\LocalLow\\Microsoft\\CryptnetUrlCache\\', + '\\AppData\\Local\\Microsoft\\Internet Explorer\\Recovery\\High\\', '\\AppData\\Local\\Microsoft\\Windows\\Caches\\', - '\\AppData\\Local\\Microsoft\\Windows\WebCache\\', '\\AppData\\Local\\Microsoft\\Windows\\Explorer\\thumbcache', + '\\AppData\\Local\\Microsoft\\Windows\\History\\History.', + '\\AppData\\Local\\Microsoft\\Windows\\Temporary Internet Files\\Content.', + '\\AppData\\Local\\Microsoft\\Windows\\WebCache\\', + '\\AppData\\Local\\Temp\\.*tmp$', + '\\AppData\\LocalLow\\Microsoft\\CryptnetUrlCache\\', + '\\AppData\\LocalLow\\Microsoft\\Internet Explorer\\Services\\search_', + '\\AppData\\Roaming\\Microsoft\\Office\\Recent\\', + '\\AppData\\Roaming\\Microsoft\\Windows\\Cookies\\', + '\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\', + 'C:\\ProgramData\\Microsoft\\OfficeSoftwareProtectionPlatform\\Cache\\cache.dat', + 'C:\\Windows\\Prefetch\\', '\\AppData\\Roaming\\Adobe\\Acrobat\\9.0\\SharedDataEvents-journal', '\\AppData\\Roaming\\Adobe\\Acrobat\\9.0\\UserCache.bin', @@ -460,14 +462,16 @@ def cleanup_regkey(item): r'\\Software\\Microsoft\\Internet Explorer\\Main\\WindowsSearch', r'\\Software\\Microsoft\\Office\\[0-9\.]+\\', r'\\Software\\Microsoft\\Office\\Common\\Smart Tag\\', - r'\\SOFTWARE\\Microsoft\\OfficeSoftwareProtectionPlatform\\', + r'\\Software\\Microsoft\\OfficeSoftwareProtectionPlatform\\', r'\\Software\\Microsoft\\Shared Tools\\Panose\\', r'\\Software\\Microsoft\\Tracing\\', r'\\Software\\Microsoft\\Tracing\\powershell_RASAPI32\\', r'\\Software\\Microsoft\\Tracing\\powershell_RASMANCS\\', + r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Action Center\\', r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\\', r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\', r'\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\', + r'\\System\\CurrentControlSet\\Services\\RdyBoost\\', r'\\Usage\\SpellingAndGrammarFiles' } if list_in_string(noise_substrings, item, regex=True): From c088b13f0374aeb586e021a2aa21b10358b9b3f1 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 17 May 2018 13:47:49 +0200 Subject: [PATCH 011/401] fix: Using userConfig to define the header instead of moduleconfig --- misp_modules/modules/import_mod/csvimport.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/import_mod/csvimport.py b/misp_modules/modules/import_mod/csvimport.py index 543d67b..85b9c6b 100644 --- a/misp_modules/modules/import_mod/csvimport.py +++ b/misp_modules/modules/import_mod/csvimport.py @@ -3,11 +3,14 @@ import json, os, base64 import pymisp misperrors = {'error': 'Error'} -mispattributes = {'inputSource': ['file'], 'output': ['MISP attributes']} moduleinfo = {'version': '0.1', 'author': 'Christian Studer', 'description': 'Import Attributes from a csv file.', 'module-type': ['import']} -moduleconfig = ['header'] +moduleconfig = [] +inputSource = ['file'] +userConfig = {'header': { + 'type': 'String', + 'message': 'Define the header of the csv file, with types (included in MISP attribute types or attribute fields) separated by commas.\nFor fields that do not match these types, please use space or simply nothing between commas.\nFor instance: ip-src,domain, ,timestamp'}} duplicatedFields = {'mispType': {'mispComment': 'comment'}, 'attrField': {'attrComment': 'comment'}} @@ -121,7 +124,18 @@ def handler(q=False): return r def introspection(): - return mispattributes + modulesetup = {} + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup def version(): moduleinfo['config'] = moduleconfig From dba8bd8c5bb0b6cc9e24cd938421240059f5b9f8 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 17 May 2018 16:24:11 +0200 Subject: [PATCH 012/401] fix: Avoid trying to build attributes with not intended fields - Previously: if the header field is not an attribute type, then it was added as an attribute field. PyMISP then used to skip it if needed - Now: Those fields are discarded before they are put in an attribute --- misp_modules/modules/import_mod/csvimport.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/import_mod/csvimport.py b/misp_modules/modules/import_mod/csvimport.py index 85b9c6b..7bea557 100644 --- a/misp_modules/modules/import_mod/csvimport.py +++ b/misp_modules/modules/import_mod/csvimport.py @@ -14,6 +14,7 @@ userConfig = {'header': { duplicatedFields = {'mispType': {'mispComment': 'comment'}, 'attrField': {'attrComment': 'comment'}} +attributesFields = ['type', 'value', 'category', 'to_ids', 'comment', 'distribution'] class CsvParser(): def __init__(self, header): @@ -96,9 +97,12 @@ class CsvParser(): elif h in duplicatedFields['attrField']: # fields that should be considered as attribute fields head.append(duplicatedFields['attrField'].get(h)) - # otherwise, it is an attribute field - else: + # or, it could be an attribute field + elif h in attributesFields: head.append(h) + # otherwise, it is not defined + else: + head.append('') # return list of indexes of the misp types, list of the misp types, remaining fields that will be attribute fields return list2pop, misp, list(reversed(head)) From 1fb72f3c7a645a9674122d54135851a3d1c3997e Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Fri, 18 May 2018 11:33:53 +0200 Subject: [PATCH 013/401] add: Added user config to specify if there is a header in the csv to import --- misp_modules/modules/import_mod/csvimport.py | 32 ++++++++++++-------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/misp_modules/modules/import_mod/csvimport.py b/misp_modules/modules/import_mod/csvimport.py index 7bea557..9342a9f 100644 --- a/misp_modules/modules/import_mod/csvimport.py +++ b/misp_modules/modules/import_mod/csvimport.py @@ -10,15 +10,21 @@ moduleconfig = [] inputSource = ['file'] userConfig = {'header': { 'type': 'String', - 'message': 'Define the header of the csv file, with types (included in MISP attribute types or attribute fields) separated by commas.\nFor fields that do not match these types, please use space or simply nothing between commas.\nFor instance: ip-src,domain, ,timestamp'}} + 'message': 'Define the header of the csv file, with types (included in MISP attribute types or attribute fields) separated by commas.\nFor fields that do not match these types, please use space or simply nothing between commas.\nFor instance: ip-src,domain, ,timestamp'}, + 'has_header':{ + 'type': 'Boolean', + 'message': 'Tick this box ONLY if there is a header line, NOT COMMENTED, in the file (which will be skipped atm).' + }} duplicatedFields = {'mispType': {'mispComment': 'comment'}, 'attrField': {'attrComment': 'comment'}} attributesFields = ['type', 'value', 'category', 'to_ids', 'comment', 'distribution'] class CsvParser(): - def __init__(self, header): + def __init__(self, header, has_header): self.header = header + self.fields_number = len(header) + self.has_header = has_header self.attributes = [] def parse_data(self, data): @@ -27,12 +33,12 @@ class CsvParser(): l = line.split('#')[0].strip() if '#' in line else line.strip() if l: return_data.append(l) - self.data = return_data + self.data = return_data[1:] if self.has_header else return_data # find which delimiter is used - self.delimiter, self.length = self.findDelimiter() + self.delimiter = self.find_delimiter() - def findDelimiter(self): - n = len(self.header) + def find_delimiter(self): + n = self.fields_number if n > 1: tmpData = [] for da in self.data: @@ -41,11 +47,11 @@ class CsvParser(): if da.count(d) == (n-1): tmp.append(d) if len(tmp) == 1 and tmp == tmpData: - return tmpData[0], n + return tmpData[0] else: tmpData = tmp else: - return None, 1 + return None def buildAttributes(self): # if there is only 1 field of data @@ -63,7 +69,7 @@ class CsvParser(): datamisp = [] datasplit = data.split(self.delimiter) # in case there is an empty line or an error - if len(datasplit) != self.length: + if len(datasplit) != self.fields_number: continue # pop from the line data that matches with a misp type, using the list of indexes for l in list2pop: @@ -118,9 +124,11 @@ def handler(q=False): if not request.get('config') and not request['config'].get('header'): misperrors['error'] = "Configuration error" return misperrors - config = request['config'].get('header').split(',') - config = [c.strip() for c in config] - csv_parser = CsvParser(config) + header = request['config'].get('header').split(',') + header = [c.strip() for c in header] + has_header = request['config'].get('has_header') + has_header = True if has_header == '1' else False + csv_parser = CsvParser(header, has_header) csv_parser.parse_data(data.split('\n')) # build the attributes csv_parser.buildAttributes() From 2b509a2fd3f7f7df769488def9014a683cef9477 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Fri, 18 May 2018 11:38:13 +0200 Subject: [PATCH 014/401] Updated delimiter finder function --- misp_modules/modules/import_mod/csvimport.py | 43 +++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/misp_modules/modules/import_mod/csvimport.py b/misp_modules/modules/import_mod/csvimport.py index 9342a9f..5ccf287 100644 --- a/misp_modules/modules/import_mod/csvimport.py +++ b/misp_modules/modules/import_mod/csvimport.py @@ -19,6 +19,7 @@ userConfig = {'header': { duplicatedFields = {'mispType': {'mispComment': 'comment'}, 'attrField': {'attrComment': 'comment'}} attributesFields = ['type', 'value', 'category', 'to_ids', 'comment', 'distribution'] +delimiters = [',', ';', '|', '/', '\t', ' '] class CsvParser(): def __init__(self, header, has_header): @@ -29,29 +30,31 @@ class CsvParser(): def parse_data(self, data): return_data = [] - for line in data: - l = line.split('#')[0].strip() if '#' in line else line.strip() - if l: - return_data.append(l) + if self.fields_number == 1: + for line in data: + l = line.split('#')[0].strip() + if l: + return_data.append(l) + self.delimiter = None + else: + self.delimiter_count = dict([(d, 0) for d in delimiters]) + for line in data: + l = line.split('#')[0].strip() + if l: + self.parse_delimiter(l) + return_data.append(l) + # find which delimiter is used + self.delimiter = self.find_delimiter() self.data = return_data[1:] if self.has_header else return_data - # find which delimiter is used - self.delimiter = self.find_delimiter() + + def parse_delimiter(self, line): + for d in delimiters: + if line.count(d) >= (self.fields_number - 1): + self.delimiter_count[d] += 1 def find_delimiter(self): - n = self.fields_number - if n > 1: - tmpData = [] - for da in self.data: - tmp = [] - for d in (';', '|', '/', ',', '\t', ' ',): - if da.count(d) == (n-1): - tmp.append(d) - if len(tmp) == 1 and tmp == tmpData: - return tmpData[0] - else: - tmpData = tmp - else: - return None + _, delimiter = max((n, v) for v, n in self.delimiter_count.items()) + return delimiter def buildAttributes(self): # if there is only 1 field of data From 9664127b85b14dc49b4ba0e9fe7ee7e5b2bf9945 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 29 May 2018 21:54:22 +0200 Subject: [PATCH 015/401] add: new expansion module to check hashes against hashdd.com including NSLR dataset. --- misp_modules/modules/expansion/__init__.py | 5 +-- misp_modules/modules/expansion/hashdd.py | 41 ++++++++++++++++++++++ tests/bodyhashdd.json | 1 + tests/{query-cve.sh => query-hashdd.sh} | 2 +- 4 files changed, 44 insertions(+), 5 deletions(-) create mode 100755 misp_modules/modules/expansion/hashdd.py create mode 100644 tests/bodyhashdd.json rename tests/{query-cve.sh => query-hashdd.sh} (61%) diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index e40e844..a9389e0 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,6 +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'] +__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'] diff --git a/misp_modules/modules/expansion/hashdd.py b/misp_modules/modules/expansion/hashdd.py new file mode 100755 index 0000000..beeaf8e --- /dev/null +++ b/misp_modules/modules/expansion/hashdd.py @@ -0,0 +1,41 @@ +import json +import requests + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['md5'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Alexandre Dulaunoy', 'description': 'An expansion module to check hashes against hashdd.com including NSLR dataset.', 'module-type': ['hover']} +moduleconfig = [] +hashddapi_url = 'https://api.hashdd.com/' + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + if not request.get('md5'): + misperrors['error'] = 'MD5 hash value is missing missing' + return misperrors + v = request.get('md5').upper() + r = requests.post(hashddapi_url, data={'hash':v}) + if r.status_code == 200: + state = json.loads(r.text) + if state: + if state.get(v): + summary = state[v]['known_level'] + else: + summary = 'Unknown hash' + else: + misperrors['error'] = '{} API not accessible'.format(hashddapi_url) + return misperrors['error'] + + r = {'results': [{'types': mispattributes['output'], 'values': summary}]} + return r + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/tests/bodyhashdd.json b/tests/bodyhashdd.json new file mode 100644 index 0000000..b6d256c --- /dev/null +++ b/tests/bodyhashdd.json @@ -0,0 +1 @@ +{"module": "hashdd", "md5": "838DE99E82C5B9753BAC96D82C1A8DCB"} diff --git a/tests/query-cve.sh b/tests/query-hashdd.sh similarity index 61% rename from tests/query-cve.sh rename to tests/query-hashdd.sh index 215de4f..4d73663 100755 --- a/tests/query-cve.sh +++ b/tests/query-hashdd.sh @@ -1 +1 @@ -curl -s http://127.0.0.1:6666/query -H "Content-Type: application/json" --data @bodycve.json -X POST +curl -s http://127.0.0.1:6666/query -H "Content-Type: application/json" --data @bodyhashdd.json -X POST From 0af064ac40912d7f74c3d5ae31e1a2186a104ef4 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 29 May 2018 21:57:38 +0200 Subject: [PATCH 016/401] fix: missing cve module test --- tests/query-cve.sh | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/query-cve.sh diff --git a/tests/query-cve.sh b/tests/query-cve.sh new file mode 100644 index 0000000..215de4f --- /dev/null +++ b/tests/query-cve.sh @@ -0,0 +1 @@ +curl -s http://127.0.0.1:6666/query -H "Content-Type: application/json" --data @bodycve.json -X POST From 1e1e69416936e5eef955208a244c270a353800b0 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Wed, 30 May 2018 06:56:42 +0200 Subject: [PATCH 017/401] add: mixing modules --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 67ba189..590fbac 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [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). * [Farsight DNSDB Passive DNS](misp_modules/modules/expansion/farsight_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [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. * [OTX](misp_modules/modules/expansion/otx.py) - an expansion module for [OTX](https://otx.alienvault.com/). @@ -45,7 +46,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ### Export modules * [CEF](misp_modules/modules/export_mod/cef_export.py) module to export Common Event Format (CEF). -* [GoAML export](misp_modules/modules/export_mod/goamlexport.py) module to export in GoAML format. +* [GoAML export](misp_modules/modules/export_mod/goamlexport.py) module to export in [GoAML format](http://goaml.unodc.org/goaml/en/index.html). * [Lite Export](misp_modules/modules/export_mod/liteexport.py) module to export a lite event. * [Simple PDF export](misp_modules/modules/export_mod/pdfexport.py) module to export in PDF (required: asciidoctor-pdf). * [ThreatConnect](misp_modules/modules/export_mod/threat_connect_export.py) module to export in ThreatConnect CSV format. @@ -56,6 +57,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [CSV import](misp_modules/modules/import_mod/csvimport.py) Customizable CSV import module. * [Cuckoo JSON](misp_modules/modules/import_mod/cuckooimport.py) Cuckoo JSON import. * [Email Import](misp_modules/modules/import_mod/email_import.py) Email import module for MISP to import basic metadata. +* [GoAML import](misp_modules/modules/import_mod/) Module to import [GoAML](http://goaml.unodc.org/goaml/en/index.html) XML format. * [OCR](misp_modules/modules/import_mod/ocr.py) Optical Character Recognition (OCR) module for MISP to import attributes from images, scan or faxes. * [OpenIOC](misp_modules/modules/import_mod/openiocimport.py) OpenIOC import based on PyMISP library. * [stiximport](misp_modules/modules/import_mod/stiximport.py) - An import module to process STIX xml/json. From 0b0f57b30c349aabe5f43749f33c33829d78b3cd Mon Sep 17 00:00:00 2001 From: Andras Iklody Date: Wed, 6 Jun 2018 08:31:41 +0200 Subject: [PATCH 018/401] Update countrycode.py --- misp_modules/modules/expansion/countrycode.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/countrycode.py b/misp_modules/modules/expansion/countrycode.py index af58fc6..9f22c40 100755 --- a/misp_modules/modules/expansion/countrycode.py +++ b/misp_modules/modules/expansion/countrycode.py @@ -20,10 +20,12 @@ common_tlds = {"com":"Commercial (Worldwide)", "gov":"Government (USA)" } -codes = requests.get("http://www.geognos.com/api/en/countries/info/all.json").json() +codes = False def handler(q=False): global codes + if not codes: + codes = requests.get("http://www.geognos.com/api/en/countries/info/all.json").json() if q is False: return False request = json.loads(q) From e6bac113baaf27981aa431f1f9c218bda49d0e17 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 8 Jun 2018 16:38:41 +0200 Subject: [PATCH 019/401] add onyphe module --- REQUIREMENTS | 1 + misp_modules/modules/expansion/onyphe.py | 65 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 misp_modules/modules/expansion/onyphe.py diff --git a/REQUIREMENTS b/REQUIREMENTS index 9e383d4..0a0c85a 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -14,6 +14,7 @@ asnhistory git+https://github.com/Rafiot/uwhoisd.git@testing#egg=uwhois&subdirectory=client git+https://github.com/MISP/MISP-STIX-Converter.git#egg=misp_stix_converter git+https://github.com/MISP/PyMISP.git#egg=pymisp +git+https://github.com/sebdraven/pyonyphe#egg=pyonyphe pillow pytesseract SPARQLWrapper diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py new file mode 100644 index 0000000..16a4e94 --- /dev/null +++ b/misp_modules/modules/expansion/onyphe.py @@ -0,0 +1,65 @@ +import json +# -*- coding: utf-8 -*- + +import json +try: + from onyphe import Onyphe +except ImportError: + print("pyonyphe module not installed.") + +misperrors = {'error': 'Error'} + +mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domains'], 'output': ['freetext']} +# possible module-types: 'expansion', 'hover' or both +moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', + 'description': 'Query on Onyphe', + 'module-type': ['expansion', 'hover']} + +# config fields that your code expects from the site admin +moduleconfig = ['apikey'] + + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + + if not request.get('config') and not (request['config'].get('apikey')): + misperrors['error'] = 'Onyphe authentication is missing' + return misperrors + + api = Onyphe(request['config'].get('apikey')) + + if not api: + misperrors['error'] = 'Onyphe Error instance api' + + ip = '' + if request.get('ip-src'): + ip = request['ip-src'] + elif request.get('ip-dst'): + ip = request['ip-dst'] + else: + misperrors['error'] = "Unsupported attributes type" + return misperrors + + return handle_expansion(api, ip, misperrors) + + +def handle_expansion(api, ip, misperrors): + result = api.ip(ip) + if result['status'] == 'nok': + misperrors['error'] = result['message'] + return misperrors + + return {'results': [{'types': mispattributes['output'], + 'values': json.dumps(result)}]} + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo \ No newline at end of file From 6eeca0fba1efda5605c8fabedc0c9cb2895c0229 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 8 Jun 2018 17:53:50 +0200 Subject: [PATCH 020/401] add pastebin url imports --- .idea/vcs.xml | 6 ++++++ misp_modules/modules/expansion/onyphe.py | 16 +++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 16a4e94..ace09ef 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -9,7 +9,7 @@ except ImportError: misperrors = {'error': 'Error'} -mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domains'], 'output': ['freetext']} +mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domains'], 'output': ['hostname', 'domain', 'ip-src', 'ip-dst','url']} # possible module-types: 'expansion', 'hover' or both moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', 'description': 'Query on Onyphe', @@ -48,12 +48,22 @@ def handler(q=False): def handle_expansion(api, ip, misperrors): result = api.ip(ip) + if result['status'] == 'nok': misperrors['error'] = result['message'] return misperrors - return {'results': [{'types': mispattributes['output'], - 'values': json.dumps(result)}]} + categories = list(set([item['@category'] for item in result['results']])) + + result_filtered = [] + urls_pasties = [] + for r in result['results']: + if r['@category'] == 'pastries': + if r['@type'] == 'pastebin': + urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) + result_filtered.append({'type': ['url'], 'values': urls_pasties}) + + return result_filtered def introspection(): From f18f8fe05ac85b9d5c3ce312b5c43024ff9d5a3b Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 8 Jun 2018 18:01:58 +0200 Subject: [PATCH 021/401] correct a bug --- misp_modules/modules/expansion/onyphe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index ace09ef..ce68262 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -55,13 +55,13 @@ def handle_expansion(api, ip, misperrors): categories = list(set([item['@category'] for item in result['results']])) - result_filtered = [] + result_filtered = {"results": []} urls_pasties = [] for r in result['results']: if r['@category'] == 'pastries': if r['@type'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) - result_filtered.append({'type': ['url'], 'values': urls_pasties}) + result_filtered['results'].append({'type': ['url'], 'values': urls_pasties}) return result_filtered From 3ec15358977ba79e6dae485da1811d02e8dc6ab1 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 8 Jun 2018 18:09:59 +0200 Subject: [PATCH 022/401] correct key in map result --- misp_modules/modules/expansion/onyphe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index ce68262..4542fc7 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -61,7 +61,7 @@ def handle_expansion(api, ip, misperrors): if r['@category'] == 'pastries': if r['@type'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) - result_filtered['results'].append({'type': ['url'], 'values': urls_pasties}) + result_filtered['results'].append({'types': ['url'], 'values': urls_pasties}) return result_filtered From cad35b5332d083004b90f103796bc2ceda7d7660 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 8 Jun 2018 18:11:04 +0200 Subject: [PATCH 023/401] error indentation --- misp_modules/modules/expansion/onyphe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 4542fc7..b42c86b 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -61,7 +61,7 @@ def handle_expansion(api, ip, misperrors): if r['@category'] == 'pastries': if r['@type'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) - result_filtered['results'].append({'types': ['url'], 'values': urls_pasties}) + result_filtered['results'].append({'types': ['url'], 'values': urls_pasties}) return result_filtered From 04032d110c2803ba22d81c4950c574632f2cf09c Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 8 Jun 2018 18:31:08 +0200 Subject: [PATCH 024/401] add as number with onyphe --- misp_modules/modules/expansion/onyphe.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index b42c86b..854fc87 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -55,14 +55,17 @@ def handle_expansion(api, ip, misperrors): categories = list(set([item['@category'] for item in result['results']])) - result_filtered = {"results": []} + result_filtered = {"results": []} urls_pasties = [] + asn_list = [] for r in result['results']: if r['@category'] == 'pastries': if r['@type'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) + elif r['@category'] == 'synscan': + ans_list = r['asn'] result_filtered['results'].append({'types': ['url'], 'values': urls_pasties}) - + result_filtered['results'].append({'types': ['AS'], 'values': asn_list}) return result_filtered From 735e6260588bce574dad615c597bba2ff97a017e Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 10:41:05 +0200 Subject: [PATCH 025/401] add as number with onyphe --- misp_modules/modules/expansion/onyphe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 854fc87..53f0eba 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -63,9 +63,9 @@ def handle_expansion(api, ip, misperrors): if r['@type'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) elif r['@category'] == 'synscan': - ans_list = r['asn'] + asn_list.append(r['asn']) result_filtered['results'].append({'types': ['url'], 'values': urls_pasties}) - result_filtered['results'].append({'types': ['AS'], 'values': asn_list}) + result_filtered['results'].append({'types': ['AS'], 'values': list(set(asn_list))}) return result_filtered From ef035d051bb5002693838ea04849ada8710d468d Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 10:54:06 +0200 Subject: [PATCH 026/401] add category --- misp_modules/modules/expansion/onyphe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 53f0eba..2a0b913 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -64,7 +64,8 @@ def handle_expansion(api, ip, misperrors): urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) elif r['@category'] == 'synscan': asn_list.append(r['asn']) - result_filtered['results'].append({'types': ['url'], 'values': urls_pasties}) + result_filtered['results'].append({'types': ['url'], 'values': urls_pasties, + 'category': 'External analysis'}) result_filtered['results'].append({'types': ['AS'], 'values': list(set(asn_list))}) return result_filtered From 0a543ca0d52515f0e9b5b12a2ae1231772ad46a1 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 10:55:44 +0200 Subject: [PATCH 027/401] change type --- misp_modules/modules/expansion/onyphe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 2a0b913..33aa69d 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -65,7 +65,7 @@ def handle_expansion(api, ip, misperrors): elif r['@category'] == 'synscan': asn_list.append(r['asn']) result_filtered['results'].append({'types': ['url'], 'values': urls_pasties, - 'category': 'External analysis'}) + 'category': ['External analysis']}) result_filtered['results'].append({'types': ['AS'], 'values': list(set(asn_list))}) return result_filtered From f069cd9bf47cbb16a53ca9bfa01c70fbe7bbe514 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 10:56:40 +0200 Subject: [PATCH 028/401] change keys --- misp_modules/modules/expansion/onyphe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 33aa69d..9f4f942 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -65,7 +65,7 @@ def handle_expansion(api, ip, misperrors): elif r['@category'] == 'synscan': asn_list.append(r['asn']) result_filtered['results'].append({'types': ['url'], 'values': urls_pasties, - 'category': ['External analysis']}) + 'categories': ['External analysis']}) result_filtered['results'].append({'types': ['AS'], 'values': list(set(asn_list))}) return result_filtered From 7580c63433f1b79a5146ff6e1d333a1c73900f55 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 10:59:06 +0200 Subject: [PATCH 029/401] add category for AS number --- misp_modules/modules/expansion/onyphe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 9f4f942..ea59ea2 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -66,7 +66,8 @@ def handle_expansion(api, ip, misperrors): asn_list.append(r['asn']) result_filtered['results'].append({'types': ['url'], 'values': urls_pasties, 'categories': ['External analysis']}) - result_filtered['results'].append({'types': ['AS'], 'values': list(set(asn_list))}) + result_filtered['results'].append({'types': ['AS'], 'values': list(set(asn_list)), + 'categories': ['Network activity']}) return result_filtered From 2cbe6ac9cad62d42daa0c13b2be6eb25fae87132 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 11 Jun 2018 11:24:55 +0200 Subject: [PATCH 030/401] Updated requirements to avoid version issues in the MISP packer installation script --- REQUIREMENTS | 3 --- 1 file changed, 3 deletions(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index 9e383d4..43dc97d 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -1,5 +1,3 @@ -stix -cybox tornado dnspython requests @@ -12,7 +10,6 @@ pyeupi ipasn-redis asnhistory git+https://github.com/Rafiot/uwhoisd.git@testing#egg=uwhois&subdirectory=client -git+https://github.com/MISP/MISP-STIX-Converter.git#egg=misp_stix_converter git+https://github.com/MISP/PyMISP.git#egg=pymisp pillow pytesseract From 2e0e63fad60050e163ad0e6aa898e775fab04d84 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 11:25:17 +0200 Subject: [PATCH 031/401] add targeting os --- misp_modules/modules/expansion/onyphe.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index ea59ea2..30bb179 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -58,16 +58,22 @@ def handle_expansion(api, ip, misperrors): result_filtered = {"results": []} urls_pasties = [] asn_list = [] + os_list = [] for r in result['results']: if r['@category'] == 'pastries': if r['@type'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) elif r['@category'] == 'synscan': asn_list.append(r['asn']) + os_list.append(r['os']) result_filtered['results'].append({'types': ['url'], 'values': urls_pasties, 'categories': ['External analysis']}) result_filtered['results'].append({'types': ['AS'], 'values': list(set(asn_list)), 'categories': ['Network activity']}) + + result_filtered['results'].append({'types': ['target-machine'], + 'values': list(set(os_list)), + 'categories': ['Targeting data']}) return result_filtered From d9ee5286e3134a71a1e2f19f24c01fe4c30bdf6a Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 11:59:00 +0200 Subject: [PATCH 032/401] add domains --- misp_modules/modules/expansion/onyphe.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 30bb179..ac127ab 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -59,21 +59,41 @@ def handle_expansion(api, ip, misperrors): urls_pasties = [] asn_list = [] os_list = [] + domains_resolver = [] + domains_forward = [] for r in result['results']: if r['@category'] == 'pastries': if r['@type'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) elif r['@category'] == 'synscan': asn_list.append(r['asn']) - os_list.append(r['os']) + os_target = r['os'] + if os_target != 'Unknown': + os_list.append(r['os']) + elif r['@category'] == 'resolver' and r['@type'] =='reverse': + domains_resolver.append(r['reverse']) + elif r['@category'] == 'resolver' and r['@type'] =='forward': + domains_forward.append(r['forward']) + result_filtered['results'].append({'types': ['url'], 'values': urls_pasties, 'categories': ['External analysis']}) + result_filtered['results'].append({'types': ['AS'], 'values': list(set(asn_list)), 'categories': ['Network activity']}) result_filtered['results'].append({'types': ['target-machine'], 'values': list(set(os_list)), 'categories': ['Targeting data']}) + + result_filtered['results'].append({'types': ['domains'], + 'values': list(set(domains_resolver)), + 'categories': ['Network activity'], + 'comments': ['resolver to %s' % ip]}) + + result_filtered['results'].append({'types': ['domains'], + 'values': list(set(domains_resolver)), + 'categories': ['Network activity'], + 'comments': ['forward to %s' % ip]}) return result_filtered From 59b49f9d20c0e7cd02e7a7df747a7a0ca2332d7f Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 12:00:46 +0200 Subject: [PATCH 033/401] add domains forward --- misp_modules/modules/expansion/onyphe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index ac127ab..cb4a443 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -91,7 +91,7 @@ def handle_expansion(api, ip, misperrors): 'comments': ['resolver to %s' % ip]}) result_filtered['results'].append({'types': ['domains'], - 'values': list(set(domains_resolver)), + 'values': list(set(domains_forward)), 'categories': ['Network activity'], 'comments': ['forward to %s' % ip]}) return result_filtered From e0631c9651ed5bcb31ad6af9529dea53119b1d56 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 12:02:34 +0200 Subject: [PATCH 034/401] correct typo --- misp_modules/modules/expansion/onyphe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index cb4a443..7cd249d 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -85,12 +85,12 @@ def handle_expansion(api, ip, misperrors): 'values': list(set(os_list)), 'categories': ['Targeting data']}) - result_filtered['results'].append({'types': ['domains'], + result_filtered['results'].append({'types': ['domain'], 'values': list(set(domains_resolver)), 'categories': ['Network activity'], 'comments': ['resolver to %s' % ip]}) - result_filtered['results'].append({'types': ['domains'], + result_filtered['results'].append({'types': ['domain'], 'values': list(set(domains_forward)), 'categories': ['Network activity'], 'comments': ['forward to %s' % ip]}) From 43402fde26b4ccc6ac576691d36f60c8a5a9909a Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 12:28:40 +0200 Subject: [PATCH 035/401] correct typo --- misp_modules/modules/expansion/onyphe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 7cd249d..c99de19 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -88,12 +88,12 @@ def handle_expansion(api, ip, misperrors): result_filtered['results'].append({'types': ['domain'], 'values': list(set(domains_resolver)), 'categories': ['Network activity'], - 'comments': ['resolver to %s' % ip]}) + 'comment': ['resolver to %s' % ip]}) result_filtered['results'].append({'types': ['domain'], 'values': list(set(domains_forward)), 'categories': ['Network activity'], - 'comments': ['forward to %s' % ip]}) + 'comment': ['forward to %s' % ip]}) return result_filtered From f6b8655f64e8d0a2e1200e3b1f44c590ccb70f34 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 12:29:51 +0200 Subject: [PATCH 036/401] correct type of comments --- misp_modules/modules/expansion/onyphe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index c99de19..9dad860 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -88,12 +88,12 @@ def handle_expansion(api, ip, misperrors): result_filtered['results'].append({'types': ['domain'], 'values': list(set(domains_resolver)), 'categories': ['Network activity'], - 'comment': ['resolver to %s' % ip]}) + 'comment': 'resolver to %s' % ip }) result_filtered['results'].append({'types': ['domain'], 'values': list(set(domains_forward)), 'categories': ['Network activity'], - 'comment': ['forward to %s' % ip]}) + 'comment': 'forward to %s' % ip}) return result_filtered From 755d907580d0f4462445af673cc20d385bccd096 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 13:21:21 +0200 Subject: [PATCH 037/401] pep 8 compliant --- misp_modules/modules/expansion/onyphe.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 9dad860..7494867 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -61,6 +61,7 @@ def handle_expansion(api, ip, misperrors): os_list = [] domains_resolver = [] domains_forward = [] + for r in result['results']: if r['@category'] == 'pastries': if r['@type'] == 'pastebin': From 14695bbeb9ba7fce9f8495aadedfc0b7386d28ed Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Mon, 11 Jun 2018 13:34:45 +0200 Subject: [PATCH 038/401] correct codecov --- misp_modules/modules/expansion/onyphe.py | 39 ++++++++++++------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 7494867..86abe7a 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -19,31 +19,32 @@ moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', moduleconfig = ['apikey'] - def handler(q=False): - if q is False: - return False - request = json.loads(q) + if q: - if not request.get('config') and not (request['config'].get('apikey')): - misperrors['error'] = 'Onyphe authentication is missing' - return misperrors + request = json.loads(q) - api = Onyphe(request['config'].get('apikey')) + if not request.get('config') and not (request['config'].get('apikey')): + misperrors['error'] = 'Onyphe authentication is missing' + return misperrors - if not api: - misperrors['error'] = 'Onyphe Error instance api' + api = Onyphe(request['config'].get('apikey')) - ip = '' - if request.get('ip-src'): - ip = request['ip-src'] - elif request.get('ip-dst'): - ip = request['ip-dst'] + if not api: + misperrors['error'] = 'Onyphe Error instance api' + + ip = '' + if request.get('ip-src'): + ip = request['ip-src'] + elif request.get('ip-dst'): + ip = request['ip-dst'] + else: + misperrors['error'] = "Unsupported attributes type" + return misperrors + + return handle_expansion(api, ip, misperrors) else: - misperrors['error'] = "Unsupported attributes type" - return misperrors - - return handle_expansion(api, ip, misperrors) + return False def handle_expansion(api, ip, misperrors): From a6717b53eff6ed2b076e90a3a5258eb934b7f17e Mon Sep 17 00:00:00 2001 From: sebdraven Date: Mon, 11 Jun 2018 13:56:37 +0200 Subject: [PATCH 039/401] Delete vcs.xml --- .idea/vcs.xml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 479e66cc9b574de1c7fc8a44b4819f674a11943c Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 11 Jun 2018 17:03:23 +0200 Subject: [PATCH 040/401] fix: Removed STIX related libraries, files, documentation, etc. --- README.md | 1 - setup.py | 2 - tests/stix.xml | 331 ------------------------------------------------- tests/test.py | 15 --- 4 files changed, 349 deletions(-) delete mode 100644 tests/stix.xml diff --git a/README.md b/README.md index 590fbac..8b96814 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [GoAML import](misp_modules/modules/import_mod/) Module to import [GoAML](http://goaml.unodc.org/goaml/en/index.html) XML format. * [OCR](misp_modules/modules/import_mod/ocr.py) Optical Character Recognition (OCR) module for MISP to import attributes from images, scan or faxes. * [OpenIOC](misp_modules/modules/import_mod/openiocimport.py) OpenIOC import based on PyMISP library. -* [stiximport](misp_modules/modules/import_mod/stiximport.py) - An import module to process STIX xml/json. * [ThreatAnalyzer](misp_modules/modules/import_mod/threatanalyzer_import.py) - An import module to process ThreatAnalyzer archive.zip/analysis.json sandbox exports. * [VMRay](misp_modules/modules/import_mod/vmray_import.py) - An import module to process VMRay export. diff --git a/setup.py b/setup.py index 3bdc180..f6c3a64 100644 --- a/setup.py +++ b/setup.py @@ -33,8 +33,6 @@ setup( 'pyeupi', 'ipasn-redis', 'asnhistory', - 'stix', - 'cybox', 'pillow', 'pytesseract', 'shodan', diff --git a/tests/stix.xml b/tests/stix.xml deleted file mode 100644 index a4a60d8..0000000 --- a/tests/stix.xml +++ /dev/null @@ -1,331 +0,0 @@ - - - - CNC Server 1 - - - 82.146.166.56 - - - - - CNC Server 2 - - - 209.239.79.47 - - - - - CNC Server 3 - - - 41.213.121.180 - - - - - Watering Hole Wordpress - - - eu-society.com - - - - - Watering Hole Wordpress - - - aromatravel.org - - - - - Watering Hole Wordpress - - - bss.servebbs.com - - - - - - - Watering Hole Detected - URL Watchlist - - - - C2 List - - - C2 List - - - C2 List - - - - - - CnC Beaconing Detected - C2 - - - - - - - - - - - - - - - Malware CnC Channels - - Advantage - - - - Hosting - - - - - - - - - - - - - Fingerprinting and whitelisting during watering-hole operations - - Theft - Credential Theft - - - - Domain Registration - - - C2 List - - - C2 List - - - C2 List - - - - - - - - - - Spear-phishing in tandem with 0-day exploits - - Unauthorized Access - - - - - - - Infiltration of organisations via third party supplier/partner - - Unauthorized Access - - - - - - - Custom recon tool to compromise and identify credentials of the network - - Theft - Credential Theft - - - - - - - Multiple means of C2 communications given the diversity of the attacker toolset - - Advantage - - - - - - - rootkit communicates during the same time as network activity, encoded with an XOR key - - Advantage - - - - - - - Kernel-centric rootkit waits for network trigger before launching - - Advantage - - - - - - - Kernel centric exfiltration over TCP/UDP/DNS/ICMP/HTTP - - Theft - - - - - - - Exfiltration over HTTP/HTTPS - - Theft - - - - - - - Use of previously undocumented functions in their Kernel centric attacks - - Advantage - - - - - - - - - - - - - - - - - Privilage Escalation Vulnerability - - CVE-2013-5065 - - - - - - The Epic Turla Campaign - The Epic Turla Campaign - - Advantage - Political - - - - - - - - - - SNAKE Campaign - The SNAKE Campaign - - Advantage - Political - - - - - - - - - - - - SNAKE - -The group behind the SNAKE campaign are a top tier nation-state threat. Their capabilities extend from subtle watering-hole attacks to sophisticated server rootkits – virtually undetectable by conventional security products. -This threat actor group has been operating continuously for over a decade, infiltrating governments and strategic private sector networks in that time. The most notorious of their early campaigns led to a breach of classified US military systems, an extensive clean-up called ‘Operation Buckshot Yankee’, and led to the creation of the US Cyber Command. -Whilst the sophisticated rootkit is used for persistent access to networks, the group also leverage more straight-forward capabilities for gaining an initial toe-hold on targets. This includes the use of watering-hole attacks and basic remote access tools. - - -The group behind the SNAKE campaign are a top tier nation-state threat. Their capabilities extend from subtle watering-hole attacks to sophisticated server rootkits – virtually undetectable by conventional security products. - - - - - - SNAKE - - - Turla - - - WRAITH - - - - - - Russia - - - Moscow - - - - - snake@gmail.com - twitter.com/snake - - - Russian - - - - - Political - - - Expert - - - Advantage - Political - - - Theft - Intellectual Property - - - - diff --git a/tests/test.py b/tests/test.py index 5a56f5a..d32bd00 100644 --- a/tests/test.py +++ b/tests/test.py @@ -57,21 +57,6 @@ class TestModules(unittest.TestCase): assert("mrxcls.sys" in values) assert("mdmcpq3.PNF" in values) - def test_stix(self): - with open("tests/stix.xml", "rb") as f: - content = base64.b64encode(f.read()) - data = json.dumps({"module": "stiximport", - "data": content.decode('utf-8'), - }) - response = requests.post(self.url + "query", data=data).json() - - print("STIX :: {}".format(response)) - values = [x["values"][0] for x in response["results"]] - - assert("209.239.79.47" in values) - assert("41.213.121.180" in values) - assert("eu-society.com" in values) - def test_email_headers(self): query = {"module": "email_import"} query["config"] = {"unzip_attachments": None, From 023c35f5d85ed9c094a0ee0efb67e0241691bc45 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Thu, 14 Jun 2018 16:47:11 +0200 Subject: [PATCH 041/401] add onyphe full module and code the stub --- misp_modules/modules/expansion/onyphe_full.py | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 misp_modules/modules/expansion/onyphe_full.py diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py new file mode 100644 index 0000000..1b121a9 --- /dev/null +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -0,0 +1,126 @@ +import json +# -*- coding: utf-8 -*- + +import json +try: + from onyphe import Onyphe +except ImportError: + print("pyonyphe module not installed.") + +misperrors = {'error': 'Error'} + +mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domain'], 'output': ['hostname', 'domain', 'ip-src', 'ip-dst','url']} +# possible module-types: 'expansion', 'hover' or both +moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', + 'description': 'Query on Onyphe', + 'module-type': ['expansion', 'hover']} + +# config fields that your code expects from the site admin +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'] = 'Onyphe authentication is missing' + return misperrors + + api = Onyphe(request['config'].get('apikey')) + + if not api: + misperrors['error'] = 'Onyphe Error instance api' + + 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'] + elif request.get('hostname'): + hostname = request['hostname'] + else: + misperrors['error'] = "Unsupported attributes type" + return misperrors + + + else: + return False + + +def handle_domain(api, domain, misperrors): + pass + +def handle_ip(api, ip, misperrors): + result_filtered = {"results": []} + + r,status_ok = expand_syscan(api,ip,misperrors) + + if status_ok: + result_filtered['results'].append(r) + else: + return r + + r, status_ok = expand_datascan(api,misperrors, ip=ip) + + if status_ok: + result_filtered['results'].append(r) + else: + return r + + r, status_ok = expand_forward(api, ip,misperrors) + + if status_ok: + result_filtered['results'].append(r) + else: + return r + + r, status_ok = expand_reverse(api, ip,misperrors) + + if status_ok: + result_filtered['results'].append(r) + else: + return r + + return result_filtered + + +def expand_syscan(api, ip, misperror): + status_ok = False + r = None + + return r,status_ok + + +def expand_datascan(api, misperror,**kwargs): + status_ok = False + r = None + + return r,status_ok + + +def expand_reverse(api, ip, misperror): + status_ok = False + r = None + + return r,status_ok + + +def expand_forward(api, ip, misperror): + status_ok = False + r = None + + return r,status_ok + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo \ No newline at end of file From 8ae7210aef0ab5c17232ed60da89e889f305617e Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 11:07:33 +0200 Subject: [PATCH 042/401] add onyphe full module --- misp_modules/modules/expansion/onyphe_full.py | 71 +++++++++++++++---- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 1b121a9..7047a30 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -56,31 +56,33 @@ def handler(q=False): def handle_domain(api, domain, misperrors): pass + def handle_ip(api, ip, misperrors): result_filtered = {"results": []} - r,status_ok = expand_syscan(api,ip,misperrors) + r, status_ok = expand_syscan(api, ip, misperrors) + + if status_ok: + result_filtered['results'].append(r) + else: + misperrors['error'] = "Error syscan result" + return misperrors + + r, status_ok = expand_datascan(api, misperrors, ip=ip) if status_ok: result_filtered['results'].append(r) else: return r - r, status_ok = expand_datascan(api,misperrors, ip=ip) + r, status_ok = expand_forward(api, ip, misperrors) if status_ok: result_filtered['results'].append(r) else: return r - r, status_ok = expand_forward(api, ip,misperrors) - - if status_ok: - result_filtered['results'].append(r) - else: - return r - - r, status_ok = expand_reverse(api, ip,misperrors) + r, status_ok = expand_reverse(api, ip, misperrors) if status_ok: result_filtered['results'].append(r) @@ -94,7 +96,7 @@ def expand_syscan(api, ip, misperror): status_ok = False r = None - return r,status_ok + return r, status_ok def expand_datascan(api, misperror,**kwargs): @@ -108,14 +110,57 @@ def expand_reverse(api, ip, misperror): status_ok = False r = None - return r,status_ok + return r, status_ok def expand_forward(api, ip, misperror): status_ok = False r = None - return r,status_ok + return r, status_ok + + +def expand_pastries(api, misperror, **kwargs): + status_ok = False + r = [] + ip = None + domain = None + result = None + urls_pasties = [] + domains = [] + ips = [] + if 'ip' in kwargs: + ip = kwargs.get('ip') + result = api.pastries(ip) + + if 'domain' in kwargs: + domain = kwargs.get('domain') + result = api.pastries(domain) + + if result['status'] =='ok': + status_ok = True + for item in result['results']: + if item['@category'] == 'pastries': + if item['@type'] == 'pastebin': + urls_pasties.append('https://pastebin.com/raw/%s' % item['key']) + + if 'domain' in item: + domains.extend(item['domain']) + if 'ip' in item: + ips.extend(item['ips']) + if 'hostname' in item: + domains.extend(item['hostname']) + + r.append({'types': ['url'], 'values': urls_pasties, + 'categories': ['External analysis']}) + r.append({'types': ['domains'], 'values': list(set(domains)), + 'categories': ['Network activity']}) + + r.append({'types': ['ip-dst'], 'values': list(set(ips)), + 'categories': ['Network activity']}) + + return r, status_ok + def introspection(): return mispattributes From c14d05adefa94498b6e37fc1656481717a1a74b9 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 12:32:54 +0200 Subject: [PATCH 043/401] test patries expansion --- misp_modules/modules/expansion/onyphe_full.py | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 7047a30..220fb50 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -62,32 +62,39 @@ def handle_ip(api, ip, misperrors): r, status_ok = expand_syscan(api, ip, misperrors) + # if status_ok: + # result_filtered['results'].append(r) + # else: + # misperrors['error'] = "Error syscan result" + + r,status_ok = expand_pastries(api,misperrors,ip=ip) + if status_ok: result_filtered['results'].append(r) else: - misperrors['error'] = "Error syscan result" + misperrors['error'] = 'Error pastries result' return misperrors - r, status_ok = expand_datascan(api, misperrors, ip=ip) - - if status_ok: - result_filtered['results'].append(r) - else: - return r - - r, status_ok = expand_forward(api, ip, misperrors) - - if status_ok: - result_filtered['results'].append(r) - else: - return r - - r, status_ok = expand_reverse(api, ip, misperrors) - - if status_ok: - result_filtered['results'].append(r) - else: - return r + # r, status_ok = expand_datascan(api, misperrors, ip=ip) + # + # if status_ok: + # result_filtered['results'].append(r) + # else: + # return r + # + # r, status_ok = expand_forward(api, ip, misperrors) + # + # if status_ok: + # result_filtered['results'].append(r) + # else: + # return r + # + # r, status_ok = expand_reverse(api, ip, misperrors) + # + # if status_ok: + # result_filtered['results'].append(r) + # else: + # return r return result_filtered From 7195f33f5dccdba730f6937433b92d2b3d4c15fe Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 12:34:07 +0200 Subject: [PATCH 044/401] correct error keys --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 220fb50..cad7135 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -154,7 +154,7 @@ def expand_pastries(api, misperror, **kwargs): if 'domain' in item: domains.extend(item['domain']) if 'ip' in item: - ips.extend(item['ips']) + ips.extend(item['ip']) if 'hostname' in item: domains.extend(item['hostname']) From e8aefde2ee50364e61f95fddce2dead8d7feab21 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 12:36:32 +0200 Subject: [PATCH 045/401] add logs --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index cad7135..a13df9b 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -95,7 +95,7 @@ def handle_ip(api, ip, misperrors): # result_filtered['results'].append(r) # else: # return r - + print(result_filtered) return result_filtered From 7a3c4b10846bd149d15b2d2e256c8ef9f00d8d8b Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 12:38:41 +0200 Subject: [PATCH 046/401] change add in results --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index a13df9b..c003672 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -70,7 +70,7 @@ def handle_ip(api, ip, misperrors): r,status_ok = expand_pastries(api,misperrors,ip=ip) if status_ok: - result_filtered['results'].append(r) + result_filtered['results'].extend(r) else: misperrors['error'] = 'Error pastries result' return misperrors From 5426ec5380c8395ce3b7c17677c29e7938aabee7 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 12:40:52 +0200 Subject: [PATCH 047/401] change key access domains --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index c003672..e910dc4 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -160,7 +160,7 @@ def expand_pastries(api, misperror, **kwargs): r.append({'types': ['url'], 'values': urls_pasties, 'categories': ['External analysis']}) - r.append({'types': ['domains'], 'values': list(set(domains)), + r.append({'types': ['domain'], 'values': list(set(domains)), 'categories': ['Network activity']}) r.append({'types': ['ip-dst'], 'values': list(set(ips)), From e1bc67afad5c2e7f5e04d5ac9e629200f2cc232e Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 14:41:57 +0200 Subject: [PATCH 048/401] add expansion synscan --- misp_modules/modules/expansion/onyphe_full.py | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index e910dc4..1a34d54 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -62,12 +62,12 @@ def handle_ip(api, ip, misperrors): r, status_ok = expand_syscan(api, ip, misperrors) - # if status_ok: - # result_filtered['results'].append(r) - # else: - # misperrors['error'] = "Error syscan result" + if status_ok: + result_filtered['results'].append(r) + else: + misperrors['error'] = "Error syscan result" - r,status_ok = expand_pastries(api,misperrors,ip=ip) + r, status_ok = expand_pastries(api,misperrors,ip=ip) if status_ok: result_filtered['results'].extend(r) @@ -101,7 +101,37 @@ def handle_ip(api, ip, misperrors): def expand_syscan(api, ip, misperror): status_ok = False - r = None + r = [] + asn_list = [] + os_list = [] + geoloc = [] + orgs = [] + results = api.synscan(ip) + + if results['status'] == 'ok': + status_ok = True + for elem in results['result']: + asn_list.append(elem['asn']) + os_list = elem['os'] + geoloc.append(elem['location']) + orgs.append(elem['organization']) + if os_list != 'Unknown': + os_list.append(elem['os']) + + r.append({'types': ['target-machine'], + 'values': list(set(os_list)), + 'categories': ['Targeting data']}) + + r.append({'types': ['target-location'], + 'values': list(set(geoloc)), + 'categories': ['Targeting data']}) + + r.append({'types': ['target-org'], + 'values': list(set(orgs)), + 'categories': ['Targeting data']}) + + r.append({'types': ['AS'], 'values': list(set(asn_list)), + 'categories': ['Network activity']}) return r, status_ok From 9427c766038cbac8a572fcd4004c9b92916ac5b4 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 14:45:06 +0200 Subject: [PATCH 049/401] error keys --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 1a34d54..f6c334e 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -110,7 +110,7 @@ def expand_syscan(api, ip, misperror): if results['status'] == 'ok': status_ok = True - for elem in results['result']: + for elem in results['results']: asn_list.append(elem['asn']) os_list = elem['os'] geoloc.append(elem['location']) From 3a4294391fa83024779b546a94c4a07555e78177 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 14:48:18 +0200 Subject: [PATCH 050/401] error type --- misp_modules/modules/expansion/onyphe_full.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index f6c334e..9b9c06c 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -112,10 +112,10 @@ def expand_syscan(api, ip, misperror): status_ok = True for elem in results['results']: asn_list.append(elem['asn']) - os_list = elem['os'] + os_target = elem['os'] geoloc.append(elem['location']) orgs.append(elem['organization']) - if os_list != 'Unknown': + if os_target != 'Unknown': os_list.append(elem['os']) r.append({'types': ['target-machine'], @@ -131,7 +131,7 @@ def expand_syscan(api, ip, misperror): 'categories': ['Targeting data']}) r.append({'types': ['AS'], 'values': list(set(asn_list)), - 'categories': ['Network activity']}) + 'categories': ['Network activity']}) return r, status_ok From d1e72676f18bb44fe770b9031fb720ee96727ca4 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 14:50:48 +0200 Subject: [PATCH 051/401] error method --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 9b9c06c..27ed2f7 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -63,7 +63,7 @@ def handle_ip(api, ip, misperrors): r, status_ok = expand_syscan(api, ip, misperrors) if status_ok: - result_filtered['results'].append(r) + result_filtered['results'].extend(r) else: misperrors['error'] = "Error syscan result" From 7eba7c0386cd29cc8b350b6134841e0557a9e940 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 14:53:08 +0200 Subject: [PATCH 052/401] error loops --- misp_modules/modules/expansion/onyphe_full.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 27ed2f7..0dad6fd 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -118,19 +118,19 @@ def expand_syscan(api, ip, misperror): if os_target != 'Unknown': os_list.append(elem['os']) - r.append({'types': ['target-machine'], + r.append({'types': ['target-machine'], 'values': list(set(os_list)), 'categories': ['Targeting data']}) - r.append({'types': ['target-location'], + r.append({'types': ['target-location'], 'values': list(set(geoloc)), 'categories': ['Targeting data']}) - r.append({'types': ['target-org'], + r.append({'types': ['target-org'], 'values': list(set(orgs)), 'categories': ['Targeting data']}) - r.append({'types': ['AS'], 'values': list(set(asn_list)), + r.append({'types': ['AS'], 'values': list(set(asn_list)), 'categories': ['Network activity']}) return r, status_ok From 915747073a2f917b76261106f6ecb6021febfef6 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 15:05:00 +0200 Subject: [PATCH 053/401] add comment of attributes --- misp_modules/modules/expansion/onyphe_full.py | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 0dad6fd..eaf7a37 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -115,23 +115,32 @@ def expand_syscan(api, ip, misperror): os_target = elem['os'] geoloc.append(elem['location']) orgs.append(elem['organization']) - if os_target != 'Unknown': + if os_target != 'Unknown' and os_target != 'Undefined': os_list.append(elem['os']) r.append({'types': ['target-machine'], 'values': list(set(os_list)), - 'categories': ['Targeting data']}) + 'categories': ['Targeting data'], + 'comment':'OS found on %s with synscan of Onyphe' % ip}) r.append({'types': ['target-location'], 'values': list(set(geoloc)), - 'categories': ['Targeting data']}) + 'categories': ['Targeting data'], + 'comment': 'geolocalisation of %s found with synscan of Onyphe' + % ip + }) r.append({'types': ['target-org'], - 'values': list(set(orgs)), - 'categories': ['Targeting data']}) + 'values': list(set(orgs)), + 'categories': ['Targeting data'], + 'comment': 'Organisations of %s found with synscan of Onyphe' + }) - r.append({'types': ['AS'], 'values': list(set(asn_list)), - 'categories': ['Network activity']}) + r.append({'types': ['AS'], + 'values': list(set(asn_list)), + 'categories': ['Network activity'], + 'comment': 'As number of %s found with synscan of Onyphe' + }) return r, status_ok @@ -191,10 +200,12 @@ def expand_pastries(api, misperror, **kwargs): r.append({'types': ['url'], 'values': urls_pasties, 'categories': ['External analysis']}) r.append({'types': ['domain'], 'values': list(set(domains)), - 'categories': ['Network activity']}) + 'categories': ['Network activity'], + 'comment': 'domains found in pasties of Onyphe'}) r.append({'types': ['ip-dst'], 'values': list(set(ips)), - 'categories': ['Network activity']}) + 'categories': ['Network activity'], + 'comment': 'IPs found in pasties of Onyphe'}) return r, status_ok From d0f42c1772cdef93e3bb260e99b4cd0b930e1772 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 15:07:55 +0200 Subject: [PATCH 054/401] add comment of attributes --- misp_modules/modules/expansion/onyphe_full.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index eaf7a37..ba27e11 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -197,11 +197,13 @@ def expand_pastries(api, misperror, **kwargs): if 'hostname' in item: domains.extend(item['hostname']) - r.append({'types': ['url'], 'values': urls_pasties, - 'categories': ['External analysis']}) + r.append({'types': ['url'], + 'values': urls_pasties, + 'categories': ['External analysis'], + 'comment':'URLs of pasties where %s has found' % ip}) r.append({'types': ['domain'], 'values': list(set(domains)), 'categories': ['Network activity'], - 'comment': 'domains found in pasties of Onyphe'}) + 'comment': 'Domains found in pasties of Onyphe'}) r.append({'types': ['ip-dst'], 'values': list(set(ips)), 'categories': ['Network activity'], From a24b529868fc1f4b2067be8226a21f8c562bd8b9 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 15:33:21 +0200 Subject: [PATCH 055/401] add forward infos --- misp_modules/modules/expansion/onyphe_full.py | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index ba27e11..fca5af0 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -9,7 +9,9 @@ except ImportError: misperrors = {'error': 'Error'} -mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domain'], 'output': ['hostname', 'domain', 'ip-src', 'ip-dst','url']} +mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domain'], + 'output': ['hostname', 'domain', 'ip-src', 'ip-dst','url']} + # possible module-types: 'expansion', 'hover' or both moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', 'description': 'Query on Onyphe', @@ -82,12 +84,13 @@ def handle_ip(api, ip, misperrors): # else: # return r # - # r, status_ok = expand_forward(api, ip, misperrors) - # - # if status_ok: - # result_filtered['results'].append(r) - # else: - # return r + r, status_ok = expand_forward(api, ip, misperrors) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error forward result' + return # # r, status_ok = expand_reverse(api, ip, misperrors) # @@ -119,14 +122,14 @@ def expand_syscan(api, ip, misperror): os_list.append(elem['os']) r.append({'types': ['target-machine'], - 'values': list(set(os_list)), - 'categories': ['Targeting data'], - 'comment':'OS found on %s with synscan of Onyphe' % ip}) + 'values': list(set(os_list)), + 'categories': ['Targeting data'], + 'comment': 'OS found on %s with synscan of Onyphe' % ip}) r.append({'types': ['target-location'], - 'values': list(set(geoloc)), - 'categories': ['Targeting data'], - 'comment': 'geolocalisation of %s found with synscan of Onyphe' + 'values': list(set(geoloc)), + 'categories': ['Targeting data'], + 'comment': 'Geolocalisation of %s found with synscan of Onyphe' % ip }) @@ -149,7 +152,7 @@ def expand_datascan(api, misperror,**kwargs): status_ok = False r = None - return r,status_ok + return r, status_ok def expand_reverse(api, ip, misperror): @@ -161,8 +164,28 @@ def expand_reverse(api, ip, misperror): def expand_forward(api, ip, misperror): status_ok = False - r = None + r = [] + results = api.forward(ip) + domains_forward = [] + + domains = [] + if results['status'] == 'ok': + status_ok = True + + for elem in results['results']: + domains_forward.append(elem['forward']) + domains.append(elem['domain']) + + r.append({'types': ['domain'], + 'values': list(set(domains)), + 'categories': ['Network activity'], + 'comment': 'Domains of %s from forward service of Onyphe' % ip}) + + r.append({'types': ['domain'], + 'values': list(set(domains_forward)), + 'categories': ['Network activity'], + 'comment': 'Forward Domains of %s from forward service of Onyphe' % ip}) return r, status_ok From 0d120af64715b5fe5a191a8d6fa22a05e5a4c3b6 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 16:24:17 +0200 Subject: [PATCH 056/401] add reverse infos --- misp_modules/modules/expansion/onyphe_full.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index fca5af0..a2876a1 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -158,7 +158,29 @@ def expand_datascan(api, misperror,**kwargs): def expand_reverse(api, ip, misperror): status_ok = False r = None + status_ok = False + r = [] + results = api.forward(ip) + domains_reverse = [] + + domains = [] + if results['status'] == 'ok': + status_ok = True + + for elem in results['results']: + domains_reverse.append(elem['forward']) + domains.append(elem['domain']) + + r.append({'types': ['domain'], + 'values': list(set(domains)), + 'categories': ['Network activity'], + 'comment': 'Domains of %s from forward service of Onyphe' % ip}) + + r.append({'types': ['domain'], + 'values': list(set(domains_reverse)), + 'categories': ['Network activity'], + 'comment': 'Reverse Domains of %s from forward service of Onyphe' % ip}) return r, status_ok From 4a8a79c56062e77aab1f64e9602f192961064824 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 16:26:09 +0200 Subject: [PATCH 057/401] add reverse infos --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index a2876a1..f340f92 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -169,7 +169,7 @@ def expand_reverse(api, ip, misperror): status_ok = True for elem in results['results']: - domains_reverse.append(elem['forward']) + domains_reverse.append(elem['reverse']) domains.append(elem['domain']) r.append({'types': ['domain'], From d4be9d9fda875582485a9230fdd7f1cf1566fabd Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 16:29:04 +0200 Subject: [PATCH 058/401] add reverse infos --- misp_modules/modules/expansion/onyphe_full.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index f340f92..19fb207 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -92,12 +92,14 @@ def handle_ip(api, ip, misperrors): misperrors['error'] = 'Error forward result' return # - # r, status_ok = expand_reverse(api, ip, misperrors) - # - # if status_ok: - # result_filtered['results'].append(r) - # else: - # return r + r, status_ok = expand_reverse(api, ip, misperrors) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error reverse result' + return misperrors + print(result_filtered) return result_filtered From b56f8cfa36071d62a9f422c0d828868ecb9c4c94 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 16:30:56 +0200 Subject: [PATCH 059/401] add reverse infos --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 19fb207..9f4e16d 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -162,7 +162,7 @@ def expand_reverse(api, ip, misperror): r = None status_ok = False r = [] - results = api.forward(ip) + results = api.reverse(ip) domains_reverse = [] From 04e932cce063d10759a0e708ed3cb8c43d68ab55 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 17:47:11 +0200 Subject: [PATCH 060/401] add datascan expansion --- misp_modules/modules/expansion/onyphe_full.py | 67 ++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 9f4e16d..8b8e0a2 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -77,13 +77,13 @@ def handle_ip(api, ip, misperrors): misperrors['error'] = 'Error pastries result' return misperrors - # r, status_ok = expand_datascan(api, misperrors, ip=ip) - # - # if status_ok: - # result_filtered['results'].append(r) - # else: - # return r - # + r, status_ok = expand_datascan(api, misperrors, ip=ip) + + if status_ok: + result_filtered['results'].append(r) + else: + return r + r, status_ok = expand_forward(api, ip, misperrors) if status_ok: @@ -139,12 +139,13 @@ def expand_syscan(api, ip, misperror): 'values': list(set(orgs)), 'categories': ['Targeting data'], 'comment': 'Organisations of %s found with synscan of Onyphe' + % ip }) r.append({'types': ['AS'], 'values': list(set(asn_list)), 'categories': ['Network activity'], - 'comment': 'As number of %s found with synscan of Onyphe' + 'comment': 'As number of %s found with synscan of Onyphe' % ip }) return r, status_ok @@ -152,7 +153,55 @@ def expand_syscan(api, ip, misperror): def expand_datascan(api, misperror,**kwargs): status_ok = False - r = None + r = [] + ip = '' + query ='' + asn_list = [] + geoloc = [] + orgs = [] + ports = [] + if 'ip' in kwargs: + query = kwargs.get('ip') + else: + query = kwargs.get('domain') + + results = api.datascan(query) + + if results['status'] == 'ok': + for elem in results['results']: + asn_list.append(elem['asn']) + os_target = elem['os'] + geoloc.append(elem['location']) + orgs.append(elem['organization']) + ports.append(elem['port']) + + r.append({'types': ['port'], + 'values': list(set(ports)), + 'categories': ['Other'], + 'comment': 'Ports of %s found with datascan of Onyphe' + % ip + }) + + r.append({'types': ['target-location'], + 'values': list(set(geoloc)), + 'categories': ['Targeting data'], + 'comment': 'Geolocalisation of %s found with synscan of Onyphe' + % ip + }) + + r.append({'types': ['target-org'], + 'values': list(set(orgs)), + 'categories': ['Targeting data'], + 'comment': 'Organisations of %s found with synscan of Onyphe' + % ip + }) + + r.append({'types': ['AS'], + 'values': list(set(asn_list)), + 'categories': ['Network activity'], + 'comment': 'As number of %s found with synscan of Onyphe' % ip + }) + return r, status_ok From 2afd2b8aaf901a3dd89e58070917d27bbb490b02 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 17:50:28 +0200 Subject: [PATCH 061/401] correct bug --- misp_modules/modules/expansion/onyphe_full.py | 1 - 1 file changed, 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 8b8e0a2..1f6c580 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -170,7 +170,6 @@ def expand_datascan(api, misperror,**kwargs): if results['status'] == 'ok': for elem in results['results']: asn_list.append(elem['asn']) - os_target = elem['os'] geoloc.append(elem['location']) orgs.append(elem['organization']) ports.append(elem['port']) From 9195887f986cfa123fb87d3971c1cb290e8145ce Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 17:51:46 +0200 Subject: [PATCH 062/401] pep 8 --- misp_modules/modules/expansion/onyphe_full.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 1f6c580..4ca9a26 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -200,8 +200,6 @@ def expand_datascan(api, misperror,**kwargs): 'categories': ['Network activity'], 'comment': 'As number of %s found with synscan of Onyphe' % ip }) - - return r, status_ok From 153d8bd340e407e058fa97b0c2b11b04e70d5b94 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 17:56:19 +0200 Subject: [PATCH 063/401] add logs --- misp_modules/modules/expansion/onyphe_full.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 4ca9a26..acdaced 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -22,6 +22,7 @@ moduleconfig = ['apikey'] def handler(q=False): + print(q) if q: request = json.loads(q) From fe00f099f618e2628560552a6592c61030207a37 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 17:59:49 +0200 Subject: [PATCH 064/401] add logs --- misp_modules/modules/expansion/onyphe_full.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index acdaced..66c208f 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -22,7 +22,6 @@ moduleconfig = ['apikey'] def handler(q=False): - print(q) if q: request = json.loads(q) @@ -83,7 +82,8 @@ def handle_ip(api, ip, misperrors): if status_ok: result_filtered['results'].append(r) else: - return r + misperrors['error'] = 'Error datascan result' + return misperrors r, status_ok = expand_forward(api, ip, misperrors) @@ -91,8 +91,8 @@ def handle_ip(api, ip, misperrors): result_filtered['results'].extend(r) else: misperrors['error'] = 'Error forward result' - return - # + return misperrors + r, status_ok = expand_reverse(api, ip, misperrors) if status_ok: From 4166475f9e7d9906e691b581469dc8ac343c11e2 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 18:02:12 +0200 Subject: [PATCH 065/401] add logs --- misp_modules/modules/expansion/onyphe_full.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 66c208f..6982ccb 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -82,7 +82,7 @@ def handle_ip(api, ip, misperrors): if status_ok: result_filtered['results'].append(r) else: - misperrors['error'] = 'Error datascan result' + misperrors['error'] = 'Error datascan result %s' % status_ok return misperrors r, status_ok = expand_forward(api, ip, misperrors) @@ -90,7 +90,7 @@ def handle_ip(api, ip, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error forward result' + misperrors['error'] = 'Error forward result %s' % status_ok return misperrors r, status_ok = expand_reverse(api, ip, misperrors) @@ -167,6 +167,7 @@ def expand_datascan(api, misperror,**kwargs): query = kwargs.get('domain') results = api.datascan(query) + print(results) if results['status'] == 'ok': for elem in results['results']: From a9b7a10c4117472f745337c7491d6ab4dfe76521 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 18:03:34 +0200 Subject: [PATCH 066/401] set status after requests --- misp_modules/modules/expansion/onyphe_full.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 6982ccb..7168618 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -167,9 +167,10 @@ def expand_datascan(api, misperror,**kwargs): query = kwargs.get('domain') results = api.datascan(query) - print(results) + if results['status'] == 'ok': + status_ok = False for elem in results['results']: asn_list.append(elem['asn']) geoloc.append(elem['location']) From e712a31760ef29dfd41a1a911e459a3be1a4cc6d Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 18:04:12 +0200 Subject: [PATCH 067/401] set status after requests --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 7168618..1187648 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -170,7 +170,7 @@ def expand_datascan(api, misperror,**kwargs): if results['status'] == 'ok': - status_ok = False + status_ok = True for elem in results['results']: asn_list.append(elem['asn']) geoloc.append(elem['location']) From 1d1fd365693308a8eecb8ca82f62c06a43d73058 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Wed, 20 Jun 2018 18:05:28 +0200 Subject: [PATCH 068/401] change method to concat methods --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 1187648..9466d8a 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -80,7 +80,7 @@ def handle_ip(api, ip, misperrors): r, status_ok = expand_datascan(api, misperrors, ip=ip) if status_ok: - result_filtered['results'].append(r) + result_filtered['results'].extend(r) else: misperrors['error'] = 'Error datascan result %s' % status_ok return misperrors From e230c88c155c4956e2478c394c78335eb1dfcee9 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 11:59:09 +0200 Subject: [PATCH 069/401] add threat list expansion --- misp_modules/modules/expansion/onyphe_full.py | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 9466d8a..931271c 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -82,7 +82,7 @@ def handle_ip(api, ip, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error datascan result %s' % status_ok + misperrors['error'] = 'Error datascan result ' return misperrors r, status_ok = expand_forward(api, ip, misperrors) @@ -90,7 +90,7 @@ def handle_ip(api, ip, misperrors): if status_ok: result_filtered['results'].extend(r) else: - misperrors['error'] = 'Error forward result %s' % status_ok + misperrors['error'] = 'Error forward result' return misperrors r, status_ok = expand_reverse(api, ip, misperrors) @@ -101,7 +101,14 @@ def handle_ip(api, ip, misperrors): misperrors['error'] = 'Error reverse result' return misperrors - print(result_filtered) + r, status_ok = expand_threatlist(api, misperrors, ip=ip) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error threat list' + return misperrors + return result_filtered @@ -168,7 +175,6 @@ def expand_datascan(api, misperror,**kwargs): results = api.datascan(query) - if results['status'] == 'ok': status_ok = True for elem in results['results']: @@ -308,6 +314,32 @@ def expand_pastries(api, misperror, **kwargs): return r, status_ok +def expand_threatlist(api, misperror,**kwargs): + status_ok = False + r = [] + + query = None + + threat_list = [] + + if 'ip' in kwargs: + query = kwargs.get('ip') + else: + query = kwargs.get('domain') + + results = api.threatlist(query) + if results['status'] == 'ok': + status_ok = True + threat_list = ['seen %s on %s ' % (item['seen_date'], item['threatlist']) + for item in results['results']] + + r.append({'types': ['comment'], + 'categories': ['Other'], + 'values': [threat_list] + }) + + return r,status_ok + def introspection(): return mispattributes From e9c18b3d5fe01dcad008d04831f50e7d860c7efa Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 13:03:09 +0200 Subject: [PATCH 070/401] correct comments --- misp_modules/modules/expansion/onyphe_full.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 931271c..02375c7 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -187,27 +187,27 @@ def expand_datascan(api, misperror,**kwargs): 'values': list(set(ports)), 'categories': ['Other'], 'comment': 'Ports of %s found with datascan of Onyphe' - % ip + % query }) r.append({'types': ['target-location'], 'values': list(set(geoloc)), 'categories': ['Targeting data'], 'comment': 'Geolocalisation of %s found with synscan of Onyphe' - % ip + % query }) r.append({'types': ['target-org'], 'values': list(set(orgs)), 'categories': ['Targeting data'], 'comment': 'Organisations of %s found with synscan of Onyphe' - % ip + % query }) r.append({'types': ['AS'], 'values': list(set(asn_list)), 'categories': ['Network activity'], - 'comment': 'As number of %s found with synscan of Onyphe' % ip + 'comment': 'As number of %s found with synscan of Onyphe' % query }) return r, status_ok From 8d03354399f689295c68fee17a4786e9bc74f8ef Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 15:12:10 +0200 Subject: [PATCH 071/401] correct bugs --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 02375c7..5d7011f 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -335,7 +335,7 @@ def expand_threatlist(api, misperror,**kwargs): r.append({'types': ['comment'], 'categories': ['Other'], - 'values': [threat_list] + 'values': threat_list }) return r,status_ok From 96c829470dd205d6dbf007d3e19db9a74fb552d9 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 15:14:44 +0200 Subject: [PATCH 072/401] add comment --- misp_modules/modules/expansion/onyphe_full.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 5d7011f..9e7ae8c 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -335,7 +335,8 @@ def expand_threatlist(api, misperror,**kwargs): r.append({'types': ['comment'], 'categories': ['Other'], - 'values': threat_list + 'values': threat_list, + 'comment': '%s is present in threatlist' % query }) return r,status_ok From 83999d6402030fa5b78630f49cdf1f5d65db5e82 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 15:57:52 +0200 Subject: [PATCH 073/401] add domain expansion --- misp_modules/modules/expansion/onyphe_full.py | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 9e7ae8c..39f3d42 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -49,13 +49,36 @@ def handler(q=False): else: misperrors['error'] = "Unsupported attributes type" return misperrors - - else: return False def handle_domain(api, domain, misperrors): + result_filtered = {"results": []} + + r, status_ok = expand_pastries(api, misperrors, domain=domain) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error pastries result' + return misperrors + + r, status_ok = expand_datascan(api, misperrors, domain=domain) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error datascan result ' + return misperrors + + r, status_ok = expand_threatlist(api, misperrors, domain=domain) + + if status_ok: + result_filtered['results'].extend(r) + else: + misperrors['error'] = 'Error threat list' + return misperrors pass @@ -271,19 +294,18 @@ def expand_forward(api, ip, misperror): def expand_pastries(api, misperror, **kwargs): status_ok = False r = [] - ip = None - domain = None + + query = None result = None urls_pasties = [] domains = [] ips = [] if 'ip' in kwargs: - ip = kwargs.get('ip') - result = api.pastries(ip) - + query = kwargs.get('ip') if 'domain' in kwargs: - domain = kwargs.get('domain') - result = api.pastries(domain) + query = kwargs.get('domain') + + api.pastries(query) if result['status'] =='ok': status_ok = True @@ -302,7 +324,7 @@ def expand_pastries(api, misperror, **kwargs): r.append({'types': ['url'], 'values': urls_pasties, 'categories': ['External analysis'], - 'comment':'URLs of pasties where %s has found' % ip}) + 'comment':'URLs of pasties where %s has found' % query}) r.append({'types': ['domain'], 'values': list(set(domains)), 'categories': ['Network activity'], 'comment': 'Domains found in pasties of Onyphe'}) From de6a81d4883afd56a91e8af0fadc683aeac0a3ba Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 16:04:14 +0200 Subject: [PATCH 074/401] correct bugs --- misp_modules/modules/expansion/onyphe_full.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 39f3d42..8a33e3c 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -305,7 +305,7 @@ def expand_pastries(api, misperror, **kwargs): if 'domain' in kwargs: query = kwargs.get('domain') - api.pastries(query) + result = api.pastries(query) if result['status'] =='ok': status_ok = True From 396b71ef3b7481c22474fc5f6d821950a5c63268 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 16:06:34 +0200 Subject: [PATCH 075/401] add domain to expand --- misp_modules/modules/expansion/onyphe_full.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 8a33e3c..bde4d4a 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -44,8 +44,10 @@ def handler(q=False): 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 From 87b07b89b54ec1e569f14e1e2e29f4f5de18f2e9 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 16:15:34 +0200 Subject: [PATCH 076/401] add search --- misp_modules/modules/expansion/onyphe_full.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index bde4d4a..6ac4750 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -193,12 +193,13 @@ def expand_datascan(api, misperror,**kwargs): geoloc = [] orgs = [] ports = [] + if 'ip' in kwargs: query = kwargs.get('ip') + results = api.datascan(query) else: query = kwargs.get('domain') - - results = api.datascan(query) + results = api.search_datascan('domain:%s' % query) if results['status'] == 'ok': status_ok = True @@ -304,10 +305,10 @@ def expand_pastries(api, misperror, **kwargs): ips = [] if 'ip' in kwargs: query = kwargs.get('ip') + result = api.pastries(query) if 'domain' in kwargs: query = kwargs.get('domain') - - result = api.pastries(query) + result = api.search_pastries('domain:%s' % query) if result['status'] =='ok': status_ok = True @@ -348,10 +349,11 @@ def expand_threatlist(api, misperror,**kwargs): if 'ip' in kwargs: query = kwargs.get('ip') + results = api.threatlist(query) else: query = kwargs.get('domain') + results = api.search_threatlist('domain:%s' % query) - results = api.threatlist(query) if results['status'] == 'ok': status_ok = True threat_list = ['seen %s on %s ' % (item['seen_date'], item['threatlist']) From 785aac3e6b6d8474f4916f48fde3b16f9d49c4e3 Mon Sep 17 00:00:00 2001 From: Sebdraven Date: Fri, 22 Jun 2018 16:18:23 +0200 Subject: [PATCH 077/401] add return handle domains --- misp_modules/modules/expansion/onyphe_full.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 6ac4750..7a05d12 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -81,7 +81,8 @@ def handle_domain(api, domain, misperrors): else: misperrors['error'] = 'Error threat list' return misperrors - pass + + return result_filtered def handle_ip(api, ip, misperrors): From 0c6a205136600da3bbacc4f4eea70ece43f8d6c5 Mon Sep 17 00:00:00 2001 From: milkmix Date: Sat, 23 Jun 2018 15:51:38 +0200 Subject: [PATCH 078/401] initial implementation supporting regkey. mutexes support waiting osquery table --- misp_modules/modules/export_mod/__init__.py | 2 +- .../modules/export_mod/osqueryexport.py | 93 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100755 misp_modules/modules/export_mod/osqueryexport.py diff --git a/misp_modules/modules/export_mod/__init__.py b/misp_modules/modules/export_mod/__init__.py index 0034f5d..d2407a4 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__ = ['testexport','cef_export','liteexport','goamlexport','threat_connect_export','pdfexport','threatStream_misp_export', 'osqueryexport'] diff --git a/misp_modules/modules/export_mod/osqueryexport.py b/misp_modules/modules/export_mod/osqueryexport.py new file mode 100755 index 0000000..715ce07 --- /dev/null +++ b/misp_modules/modules/export_mod/osqueryexport.py @@ -0,0 +1,93 @@ +""" +Export module for coverting MISP events into OSQuery query pack. +Source: https://github.com/0xmilkmix/misp-modules/blob/master/misp_modules/modules/export_mod/osqueryexport.py +""" + +import base64 +import json +import csv +import re + + +misperrors = {"error": "Error"} + +types_to_use = ['regkey', 'mutex'] + + +userConfig = { + +}; + +moduleconfig = [] + +# fixed for now, options in the future: +# event, attribute, event-collection, attribute-collection +inputSource = ['event'] + +outputFileExtension = 'conf' +responseType = 'application/txt' + + +moduleinfo = {'version': '0.1', 'author': 'Julien Bachmann, Hacknowledge', + 'description': 'OSQuery query export module', + 'module-type': ['export']} + +# test : http://misp.vm/events/view/23 +def handle_regkey(value): + rep = {'HKCU': 'HKEY_USERS\\%', 'HKLM': 'HKEY_LOCAL_MACHINE'} + rep = dict((re.escape(k), v) for k, v in rep.items()) + pattern = re.compile("|".join(rep.keys())) + value = pattern.sub(lambda m: rep[re.escape(m.group(0))], value) + return 'SELECT * FROM registry WHERE path LIKE \'%s\';' % value + +def handle_mutex(value): + return '#waiting acceptance of Scott Lundgren PR that would allow to query Kernel Objects' + +handlers = { + 'regkey' : handle_regkey, + 'mutex' : handle_mutex +} + +def handler(q=False): + if q is False: + return False + r = {'results': []} + request = json.loads(q) + output = '' + + for event in request["data"]: + for attribute in event["Attribute"]: + if attribute['type'] in types_to_use: + output = output + handlers[attribute['type']](attribute['value']) + '\n' + r = {"response":[], "data":str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} + return r + + +def introspection(): + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + outputFileExtension + modulesetup['outputFileExtension'] = outputFileExtension + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 7c037ed090b4bb1432ecc660b6b42f7e8345ffe4 Mon Sep 17 00:00:00 2001 From: milkmix Date: Sun, 24 Jun 2018 21:09:42 +0200 Subject: [PATCH 079/401] added support for service-displayname, regkey|value --- .../modules/export_mod/osqueryexport.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/export_mod/osqueryexport.py b/misp_modules/modules/export_mod/osqueryexport.py index 715ce07..9c79d50 100755 --- a/misp_modules/modules/export_mod/osqueryexport.py +++ b/misp_modules/modules/export_mod/osqueryexport.py @@ -1,5 +1,5 @@ """ -Export module for coverting MISP events into OSQuery query pack. +Export module for coverting MISP events into OSQuery queries. Source: https://github.com/0xmilkmix/misp-modules/blob/master/misp_modules/modules/export_mod/osqueryexport.py """ @@ -11,17 +11,13 @@ import re misperrors = {"error": "Error"} -types_to_use = ['regkey', 'mutex'] - +types_to_use = ['regkey', 'regkey|value', 'mutex', 'windows-service-displayname', 'yara'] userConfig = { }; moduleconfig = [] - -# fixed for now, options in the future: -# event, attribute, event-collection, attribute-collection inputSource = ['event'] outputFileExtension = 'conf' @@ -32,7 +28,6 @@ moduleinfo = {'version': '0.1', 'author': 'Julien Bachmann, Hacknowledge', 'description': 'OSQuery query export module', 'module-type': ['export']} -# test : http://misp.vm/events/view/23 def handle_regkey(value): rep = {'HKCU': 'HKEY_USERS\\%', 'HKLM': 'HKEY_LOCAL_MACHINE'} rep = dict((re.escape(k), v) for k, v in rep.items()) @@ -40,12 +35,29 @@ def handle_regkey(value): value = pattern.sub(lambda m: rep[re.escape(m.group(0))], value) return 'SELECT * FROM registry WHERE path LIKE \'%s\';' % value +def handle_regkeyvalue(value): + key, value = value.split('|') + rep = {'HKCU': 'HKEY_USERS\\%', 'HKLM': 'HKEY_LOCAL_MACHINE'} + rep = dict((re.escape(k), v) for k, v in rep.items()) + pattern = re.compile("|".join(rep.keys())) + key = pattern.sub(lambda m: rep[re.escape(m.group(0))], key) + return 'SELECT * FROM registry WHERE path LIKE \'%s\' AND data LIKE \'%s\';' % (key, value) + def handle_mutex(value): - return '#waiting acceptance of Scott Lundgren PR that would allow to query Kernel Objects' + return 'not implemented yet' + +def handle_service(value): + return 'SELECT * FROM services WHERE display_name LIKE \'%s\' OR name like \'%s\';' % (value, value) + +def handle_yara(value): + return 'not implemented yet, not sure it\'s easily feasible w/o dropping the sig on the hosts first' handlers = { 'regkey' : handle_regkey, - 'mutex' : handle_mutex + 'regkey|value' : handle_regkeyvalue, + 'mutex' : handle_mutex, + 'windows-service-displayname' : handle_service, + 'yara' : handle_yara } def handler(q=False): From 349dd99d470bdad1c8d2a9ca758df3010a8fdd0d Mon Sep 17 00:00:00 2001 From: milkmix Date: Sun, 24 Jun 2018 21:13:56 +0200 Subject: [PATCH 080/401] added support for scheduledtasks --- misp_modules/modules/export_mod/osqueryexport.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/export_mod/osqueryexport.py b/misp_modules/modules/export_mod/osqueryexport.py index 9c79d50..11c253d 100755 --- a/misp_modules/modules/export_mod/osqueryexport.py +++ b/misp_modules/modules/export_mod/osqueryexport.py @@ -11,7 +11,7 @@ import re misperrors = {"error": "Error"} -types_to_use = ['regkey', 'regkey|value', 'mutex', 'windows-service-displayname', 'yara'] +types_to_use = ['regkey', 'regkey|value', 'mutex', 'windows-service-displayname', 'windows-scheduled-task', 'yara'] userConfig = { @@ -52,11 +52,15 @@ def handle_service(value): def handle_yara(value): return 'not implemented yet, not sure it\'s easily feasible w/o dropping the sig on the hosts first' +def handle_scheduledtask(value): + return 'SELECT * FROM scheduled_tasks WHERE name LIKE \'%s\';' % value + handlers = { 'regkey' : handle_regkey, 'regkey|value' : handle_regkeyvalue, 'mutex' : handle_mutex, 'windows-service-displayname' : handle_service, + 'windows-scheduled-task' : handle_scheduledtask, 'yara' : handle_yara } From 59b7688bdcbdc3d1d1e085ec6e8862c0c8585b33 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Thu, 28 Jun 2018 16:00:14 +0800 Subject: [PATCH 081/401] - 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 082/401] 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 083/401] 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 084/401] 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 085/401] - 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 086/401] 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 087/401] - 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 088/401] - 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 089/401] - 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 090/401] 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 091/401] 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 092/401] 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 093/401] 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 094/401] 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 095/401] 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 096/401] 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 097/401] 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 098/401] 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 099/401] 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 100/401] 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 101/401] 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 102/401] 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 103/401] - 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 104/401] - 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 105/401] - 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 106/401] - 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 107/401] - 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 108/401] - 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 109/401] 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 110/401] 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 111/401] 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 112/401] - 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 113/401] 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 114/401] 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 115/401] 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 116/401] 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 117/401] 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 118/401] 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 119/401] 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 120/401] 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 121/401] 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 122/401] 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 123/401] 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 124/401] 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 125/401] 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 126/401] 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 127/401] 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 128/401] 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 129/401] 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 130/401] 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 131/401] 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 132/401] 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 133/401] 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 134/401] 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 135/401] 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 136/401] 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 137/401] 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 138/401] 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 139/401] 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 140/401] 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 141/401] 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 142/401] 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 143/401] 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 144/401] 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 145/401] 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 146/401] 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 147/401] 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 148/401] 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 149/401] 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 150/401] 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 151/401] 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 152/401] 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 153/401] 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 154/401] 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 155/401] 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 156/401] 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 157/401] 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 158/401] 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 159/401] 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 160/401] 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 161/401] 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 162/401] 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 163/401] 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 164/401] 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 165/401] 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 166/401] 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 167/401] 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 168/401] 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 169/401] 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 170/401] 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 171/401] 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 172/401] 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 173/401] 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 174/401] 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 175/401] 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 176/401] 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 177/401] 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 178/401] 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 179/401] 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 180/401] 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 181/401] 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 182/401] 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 183/401] 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 184/401] 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 185/401] 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 186/401] 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 187/401] 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 188/401] 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 189/401] 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 190/401] 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 191/401] 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 192/401] 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 193/401] 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 194/401] 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 195/401] 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 196/401] 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 197/401] 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 198/401] 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 199/401] 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 200/401] 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 201/401] 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 202/401] 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 203/401] 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 204/401] 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 205/401] 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 206/401] 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 207/401] 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 208/401] 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 209/401] 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 210/401] 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 211/401] 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 212/401] 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 213/401] 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 214/401] 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 215/401] 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 216/401] 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 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 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 zcmeFYbx<6Cv@bY#f`$YL?oJ>@U%n;lO8iGx5XMzOR!QI^n?h-7x!{D;> zyZi2ax3+5Ez3=YZt^MP5*QuVa?wbCd^ZoRy0Z)qn zX#ft^v**vSaGpPZj*EkX_kx`81p&be+SjD7$eHO`S()jX7}?*6^0IRZaxpRi<$;3Y zk{@I~u<+5X^hWZ3dV6XI5I#fQLv2Mvp$DK6 zqM#9?Jaq$T000yWy(1A%KuqQ{2C0e;V$JarMqYELZ_lwwUXzlMGcmKUva$04`QHl& z3Q2#Ek(HBIP}I`a(bdy809jaCS%3NrhB&*py19FJdIf$D3JwVkgT}_iCnP2%r@*qZ zb8_?Y3kr)WtEy{i>*^aCJG;7jdi(kZ1}7$`re|j7<`>r1H#YxnZSU;vot~XvATF=2 zZ*Kq53k87of2#Fwn*Cq&B1GziijIzkj`fdTD5&m8K_f)RpyS4TDXD>F=J<-9C*T>8 zR7_??$8!c=%@bmCr*Ui&M!q$s(|=U^7tQ{2ihcVpY4&f5{dc_<0Jvx<$jL(^1c(C= zgREKp*#BGpw+;R`2L3k&{$IsFe-0Ox7XZW-tV6Y%=StNBP?FJm&n`TvvJiQJ0)ggV z_8_tki}xj7vljW;nu0VuTC@B)$8}^DZlN9xS}Xo6Sy`_NkJvhO_yD(TXlN@*^#A{u zFg6?%jplauU(dlf0os3?bdC*XZR@JdM-hH8Dpcw8#1cSaIMO zJaVPDtMtBW$XIm4+GMZ&?c{YkbK2Qj(y3oFP95RAVa)YreJw{UCKD)SPrXQhaJwQv zbk4r2OtONMSMYc=PPR&@J$MvgWc>;in#UpB%@5&|Gj9~5aN|kW4wYh=y+@*PY z(!K5cXz^97dvHk4Lf_Te6QIpCt}-IQR4>HbxB3pY(Mw*xHYW%&fao~${Q%@9L(uXy>Dq~pP6=yHOnWCdRoj|7MijpbA^LM0{K!-ovJ?|q$x z_Arj6jVm#Jn%0L-)SNOG+Xifh`5)4b?i(mQXR3N1$wT1s71fySSnEyuRT+2HX%idj z-h9$S!J>F(X`tGvO2jy-aL$rnP~_z8tVqFc;p{A6CTGfAFlJ)K+LTVkh!OPAu*DK$ zl$EU@d8~$u2jC>qkihu=RD%2qEK}cl0aB8&gB3cjO7;&`DLfVwtn|lRtp$kt_8#9$ zD7z8f^gVisNj;(!7Kbx&#=J~5YZ6xac@d$+8>BH6=g*NeEn;hh!iHj#M6-0)mT|2| z=u@z=_qK7*E!xclD)z1TduhFyU-Cve$BoL@Cjc>|?)&#>L%Ks6)8QH6q=cdV;RCNK zN|=YDgDK=7h>0quON($h20b6WFp=3DfF3ueCzE~6^?384YJGuWto|nSR%a*?`^=iK zP$+UtPtd>S4gbXNMf0%KTh$KHn_EAi2nL$1yxpWPmn$@Gwxm~h!!c&xI)7e5iw zbk)%8196`KjR|(*Aq$X)yPv`LL9X@L#6P>IeN+0+dzL`lRqQUKK~n;WQpPNGw|yV@nP_}tMJns-P7Kld&Ryt zXL~$-&{X&VH*($sIPr-;>>RZw&7_V9k?uUpBdy+E3r#Ni~{jzPdK((IozS z59w7&nNBspI=^ODrt@gbr)THw&s=>bf1W7$2P@`UxBGR`@_vtcvnn;erP+A=>deZ|_2Na% zVkeOcnx65tTSS-EbEXnkQF8q`rS&mH{TALzE-s$qf?DXIy~J4_Mn!B$FinvC;vbI4 ztokL8+b5@3$j)R|X5`1^J@xhp(rOJE0MY-eB0mBJXgX>%XUc(s!JUycqx-E4HIwQ$ zmDW;X`m@LHuYJHdij_dN8B4Ko)(!U)g40q9{n;{CYYqla z^~*2YFi(PFqT)L6cA zJ-t8;l0tV`tO1H!sL_yLOY>AR&+1fBqpA=smZf+nta}xF;ppnZ6yxVgIB+GFz*|Vl zQJydBYMH&%v_^y91GcNH(+IAQwn8JQ7w>KSqUkS$EuP9N46_fU4;mzVuH+~s$0iI*B0Br#s&TjgXNeFeYXC}}Jib~%$pddz$fO}36# z%wI@-CW;R}U@prR^wH{YP$WBGJo{PN_t#Ho6M@IP*D+|+IdDAp2;57_Q=xRGQ_wJ0 zQ||)Ks=XH2<VNz+3^^VreTb_h@~rWZfJyN=u$YDf-dDZ{AciGj-L0r zIZP|oYR6H{;=Pp%-0ICAbb+kBHrj6EJ&FXfUTz7clI|~J$)wBn#Iou*kEZ)Tr}E6O z8I3nD`Rs%v5z{;AZ*;Ao=Y--CPk_!pYbI7kZ!$|<vzL{XB4^L!n z-tMMm%+R(DMjZ!j?~EcYsHfpVnm@+|bM+@dKdI?XgC{O;ow1@R`C4>*)AQK`3K+!kI(|qd{MF=HL|zC4KnpE>R2R@xKc+<)JuQ3?-j=h8;L6s_K-K zT!OjBoP1QQclZOp8oj$~W1snr;fdID7So#V)KHIluS?3(%H{@D_Rib;ZL$|q6 zgT5X|C&vfalWKRInpFB!h8pKUg#i%QM2L8462x!JX67vUNaTnh7{|Cb1EwbIO+n`A zi=O$Gx9X_J7wC^f_TtNYKcn*Z73yyZekbEJv^Cw1H?%d}PP)^m%|xv9`oH!(YJ>Bo z?>*ytP5lH&pmRkK0lsIuQmD`^2tU$?STRI(!l>kTN4)?ZJSfr9*c$J4+5^=S`P?TN~`CME^6Z#6f_^vtI>TjrET2(b%n zqH8BG{V#QU(AU2?st>VhNT62+5r@HTUVtwT`HpMk0Qe<77;g@n8& zH9}wxhHiN1?68c{)M<|z^ev>#-X`@Whb`gOpwrK>Z#d@DnHU9BMcw?@hq@a-sEXN? z7t*LppsavH-Is4AH5pxbazAdm$)*J}y0Wv-qKt^@!gt4?a}lNgI42*}tZZnebBihb z^g6~aW+P_cc4}TMJ|$=Q*a&ZV2{EByfIo2YD?>wr46783!~F!bfg|-$Qw)`BC##7g29xwx$)+7e_t0szQZ;H6zqN=gI-raG>p5! z?9Vua|BI$KdLS|@@78ar4ZalF#G<0~18pB2 zS_WM9*I$T6fKwDHvwbmPXE{3-ac969p-6HUK^wDdEuDtoJ)w8~MR5z?-m}nGAN}H{ z(GkVdX3XrrmxH@YWp3zn&CefMDm|x?(e-$+@siiHdJ~wwF;TT$$C1wAGAT$NPX@#3 zq933jauviCg6`i7u8WJ5yh@9U8hA{9oE}o3W+&0i;fZ}Fi0)QmqaGo%8M|Y2p>fbVRzUk>`)#R&&aaE zkz(VN+`(V#e52Uny>;0#Q@*mq#q2i2QJQV_Q%4a!U0>NRC3o2CspC=7F;pl$%gkRW z=$~-Rz7#muHUA;7o`(A&6$PX_GY2=6KS%r|v$b7+2hjcf z(Bx9)PJHl$QRpC2Q=L;2Q-HIniD@zj*y|15-&2iE!nmoaFL#kE%R8LP7C+wY%->Gp z*555@DQS`Ip!|K{W(APYZJO)GbgDU>T!vxkIz=^wbd z{{z=PU1Et}fYz;4Os^SyIm?f4C*pBo>NWICpPaQzSWVa*Rr_*Da~NS6a)awm!hG4N~z+>8Mz=)Xajg;kH1ciq+LdJ1una8uGk zF|G){$T5Gm4YQ#P*M=-v)UW$f8zkk^|7?D@9$h0lCvqWH-R=po5I38>(U#Skj8Yzt z#vnnt0hq%;$;jQGdPJk@ALJJLZ+V5K!;oj{-1_Iy4iNgkssz%~Ksma8`egi1S4DcS z?CpfG6*g&YwMHFvuD0iHbz_W`A&R$j`ditY`-&>>U{w@&_DL}l0krfmRq@U&7Az<@ zQ11<68Sb|a0t^T^?f7!nRz6lXfwX?d8M~5AVokEZUp3s;tm^O*rb0mPQ#X$v*Uo*i{g-sSSUh!xg*Mp)15x+DIfU zP_4WB7MyUG7uGL83d#l(Sw!B}dG-Dpq zoh#*R^mLS`aALtP%5izK|EdSAuEspB**`}dQ)I$OC=x56t&zx5{tMTX#K!Mq4L$$E zeTsfxe%qSK*H{H>rsULEuQ=Trr5m8|c7rd`$&ZKWqQUnx5X8xaCrMK}H7}w2uZm0y zy~EWvazu;zI2O>E+PYqW(%J)05?4|~!wodzOd|b5B_cx^eGTY#{O<*qo(}+L%}5V4 zqR<>fVGIZ6@A}S8dhMe43&jM@sxR~OwDBi3v>;7^T&EH_D7Py1*Kw#G)i*9CQ@B10 zb5XA|&vfdk!KiaqV!MhAL#*SR_V+0#HoN3PAQ?}c-{jsVLp(F(0$k)E8I(e=6O4(Pg4f0VQgoGN6 z!w4Pn3M9OiW?%A)HjhHg+2%&-7`?Rvrb59G$t6u;-sw~-OlqoKQfEq5ZV%K2BSe#de9pw|w1BC0aByRZEq{)>Zto}@QwS4mF* zxS^Ep*lM>%gHb1-OgsZ9Xuj5OeuDM=8g%`6;LKE=b8B@ih{-AXX<1 z|BI0SiW8u&ia|^X50H3atW={0+-~KcR<6VoW4-`8-V*E^tAzp?E2|UT#2p+&)yzyR z-js=yiR3#syy;tMJgL6P9loXXXl#!8d&El>WKj3L3Ps~XiXaf4Z@6j4uxnD+8TB(Y zO+mlP1{1Jey&8(5T=!!EjxjpOk*+DUJ6X;;ik9!J#b_D6ftp5Xfwqv3qhIUV%RtEP z0{Q?-BS8X_R;NbLzMt;88xvWcubs|U ze&Fv^o|BMLYI_!h4;?FP6G+P>8xurt;O%^7&H`%}Qcmi+E<7J0F>hpMhJiBQKVM|o zVaqeR!ujVm>xIQ8>gr%A-<{EhJiLfMjyam1riyRSLPu8wAGL#;w2Ppa`sX^!mA(Fi zhTmFKHAxDF%+(5VIF+9R-uPhP7Dp+5h$im!%u`7;Ler)frv1oPmPLORxlI`^-dcd%+p1S*8b z;SWd?9O*Hd%Z#Xy0 z+d0bQN;Y9{N@G3F6GWmkmUNm5%2l*toWv5U6cvZJb2U2kL}{d^NIu7mSC;T_6x7B! zLC>PF_b_pyDjRML-(;Aqw@@fI;Ag{5_9h+Teu$+f$Y^5AFA!;2e1W=JL;h#eZRo2y z90P8JTX4lW;?e!t-A^`eetSq}6dthUx?3_Il>Qd(^$KffHsrI9eVg*IShp#j_7No( zE0;rfh+%|osELGS3KmOrY_d%59V36!Y~>5)`OLuNj}~W)k%K}g_p9WAA8*1=wAhKL zy{M2YM2!0NRWicpEwMRKdqKPfH2tmO=9i1?msTKOHXZPqXUQTzlmuAVo)h6wC^cgX z%b^815dG3>FKhtg;YhA$Ky5S|bm^i`b(U5?&EzE|7OaSbqd{{>=tDwA_}4Wja9ws` z$#MZ5K#C9YLzEjeJaHA2R$+f5G5GcV^g&_6gTo$&kf_^IPx400&2fPGKkw|c_1aMK$_<)S}2#0>j-Z}MFn<1Tvi7+wnj=L^H< zNrB&6>5SGtQ~51_gml`F@DLylLdH9YNvxL1@#stlOnNh|M`RflWH+M)E(P<_>lvpR zrKdcRUj8J4uM5HV%#m*bjE~H?W`F>^eG@PhIu^d~cUl1EpRng6bidH~mL}qq`2ggf z6dm~{m348WC9%+*@LX%(;}qyK{^`3i@_Ma?|GJMVAdKXRnJ_o1k}){&V{MeLq` z7;g*_EE#xZ^Kn+%`85l4py>x0aP~PhP=9`D*!EPul5SzoDSvGCmxr^)YyQ(g{P*~B znMWK!2~NBqF_ISk*$BHn9G2m>WBj)Kvr=y>;l4B%QQXG1mV-Z9Gn|%7*|2Jk*UI(C zyFNpY@B7D{R%Up)Ij zV>khQ5KF=X2~iB`B8c0qOrY^nT#bhgm2ne_k#oZQzGhIY%+IEu#zNDjTwD6tG~Fw$ zfm5?A1>Vy711da2ZsLDSw#&xlu4h?F_O84}=Ia}MQyDFedPT9WC%_=t%o3H&IYW_d zpDjxE2Q^6pDzN@^T%RwU+u*Vc>4m-?O)132G!%4?j~grL(9+e7wZ{!gG{t3c!93TD zS1FDF>XQ&|To&z{UiRUd*ZYns%`m)dX|swgrfzS6*5 z^=6-2SUI}03e!g9nC%Co>ILn4kz{8Yg#w@9eyytL7!+0{MtsPmH~rvETfQ2{CE6%?3ja{Y|MH##Iub1bUSk8Q=JNfrVSX!au5Hu8A_bhqeQ)Bm1Xm=?Cf zlzs4c;E1iNLM9`XY~-mum+lJ!8E0K{yx-I(ax)iua}GQK64mhz#7aEws|-QhOCy3x z7Unun73D%o& ztX?qgTTGBuR&KJq7baI!HyZ*Lwq$w?DNQebG@nW09A{#%p|O4fQ0hqxnY`TTKZwjV zP{l6qz-&53xrT4MOaRGn1lLz|_x$EqZfH8gWEZkBg?D~WLBk8J)v>&)KU(atw@gp* z@9?J`HLv;g=5oOJqgO1vVq9ozg7rwyW#h% z3XfACb!0fA_&vEu>~xF!x1FhKPWLOe>b^uodX5}X?|P1zLPhGDB+Hm~qQrb&o$}O# zY`;SnW|n>9XVh0JeCNu1?cRELFK7Xhh{D!+>ZJt1i=to{IB%>Zxk_P6>@X9;pvpHX z|FNWhF$S8U?x>qJZMU(ODldvmgB5`B0JdEv606UpWqrJ$^_3i@n;*NTC}-bJAfqR# z#Vn`%g#`cIu)H_}x7#+EdeiHdPk^A$a_ovt*$zW{Y(#<)d@x-REuS4g9!yLA^NUI3 z$7Dl=uU%S&FYl+scRoz^j`+2JezD*{m0v1UJtgapog4wvvs=_5E!vGg1dDuMG!DQ9 zqD)s<5X2RZG#ZATa!Dp>KV{KlRx2NY8dp8^12p*Iq&_k>Ml!*_*}v}^yJ<$GY|E-9 zC}p>tJe(~_QWp5?0ib(_8AzYv34pcYY0p$BlDX0*J&SmB-Dz}LjCMA`U0-7%@v#*w z>``##+sV<2QBv3c&pOE3TD3$|HL8e{JbyvI*DE*VF7;n{?|&daOo-y|5jJ0Y^_UI& z;T>y|&In*+*qB~LUDC<$6d@D81yg_olX%S)kASYu&n^;sur4&fHE?9mHXu@R`pj+RbeAEQM$& z2ZLT0h;iXEr?x=xLmS_OpQB&*Wtqh!-<>i4IOm%+{Y+JVcd?u+;HbCoBt~QaRoB~N zyHQx*LpoHP%a43t-MTW4UtR~4pd0-bNDCUN`T!+Ain1-;b(%9O5bm{b5{$TeSBBYG zmpHLy0}h_2cZwA4=^tY$x;|yu!swG zvtQV~&DS;R*g8=6ZTydfDv4JQm;IxwDEE6w4HU5tH~L4-G;3e&@%7QM%RK3$#OyYo z04{!&9NtW#B)K?7rFuf~wpp~S7_u8w7=~;bim2f*J*p_)>)f@u1%kuH&z*xgoc$VC z0juw)T!33U$FE9aD)cnT^j*F)$tskV2_QFI?aY+?T$=DI9P=72y4d=Zl@>!x5y;W3}=emwCLxHY;B$n zd6puAo30|wbA*oBS6}?0GHszOUSTbSVU`ad#md4$ImWrnOwi`BI|}-$B{5j{;^tR4 z>R0O`P&e*(few?jV##aTNqzTY#f1pP!R`-khuWJ}5y=}2t#XRhALeOzEZT?&RcBw? ze$kGcekoc?JZiL_{rLlB7sP2ycGM@qd!S5*x4D_^HT^rrfEP{AZUIVq6DrDF+ z7f~3T<6GUOsX>+p^UOnfm^na(?%g3aw3%!^z@&mAm=%SUIwY4UzWrH`7@1{Qo$Ox~ zS~AK>g|y`BL+YOPIrZWzPe+Y(y&(DY;|wdz8LuNxD08;BC-A6W^rK?LsIvYf#z_v!_FDD)imyYUBi;I46e?%1j0i`qP^@IMw) zzcK`j7b$i;9zDvG$F~O~ZHBqBR!Um4noP0(Q2bzJv^Xb5Y0VBa5|{%i8jo7MDIQhm zt62UpPjn0ad>5|+TNRb~Px?i-o zTBy~W{@9{bMm1!+V?p6Gp>HfshN0q-pJx#)w|dFc!7Xa*xXfzUgYBP&QYasIqxMbo zJ&B>hwW5o%p>nwf^x+ua-iaT_fE@EMtUOeA-`ELs_ECcJ?zXc(PY@1nxx zNrH$@*eUqr?G{d+0HJj_&KsN(W%T^|ZN%yX`>;zN8_f{8i6htNbLas$0Kc#%g6>tl z(f7Oq&W@bj`8A4_XO9H?0imtC6&|Uwrff?yF2H7;YS!o&c^& zzS6+$Hq67?MTq-%pKbY&)#u{FdcXQxE$wNprpmUs6|=9Pc`cLM3X}sJ{e4Wy756G$ zeH}ZU*Is+dPFHd)J6X+H+T4n#blp|PFMTKIv!e~8Vl@YCgb2EP z4?7pUFuSPez`2Kw8$Y4b^5?Xy10pEKE{P#jdhjU46Z%jQZVDv24LHj0Pail`*S8s- zaUVfie}W$}at%R*=W?WYk5^i^Fa>#dbf|UApJRR*1vE_K`c3ByP|&F=^z#O($g8Po zI?*sv?Jumc>vZ>?w_4IAsm0a>KX4tRA^cW47|8^1WgD*R_diEfr{0_8I&DdkVKFQu zPbVAruV(y)O$J|2@14K@=FjL%AH0cIQ)EAYS~HN1hys_YNve$2aCyE|$QCnyjskap96K zmVzKwT%S$|lTe%_gz+!s6T33wk-8x|dOpJvHx2qM-h`|7cSsX*V8NHf)la5`T4IQ?P4VYUzt-9Ys4Fqe_IX)$Rg ziewXbO(7Fg+IiBt7k>-?>IRLEEX&n|!U?d9lDF$?SdQvrd?S#VH)85clIK7zg`|Up zC6-?a%AfY9bj-6y6d%IR=FjvtkGyKrn6-7IclANa4H|}@ooY5R-t3i!z$f^aB2iSxPm+fivIhHiH5shOZ^57aEe zv%p258+wn|Sz^fV$&hBXms7PPts=J-1#<^sQ8nZic0Z^iZ4$pyComOS=v0y?#yOfr zZ=ZPMamBgZj-Za)+*b~`+=)ae@ zy8L+b6#tzOvwN)>|F(#d#g^>&mzG_r?EyS}YtQl<5!okae~v!Gb@wnuTY}H|cbzC1 z+eVMYa7g3k;OteLF~38o7urEKbVSqM+0=%(qG98hv@VQGcVX7W%LP{vts|kWS?-K@ zVAj}ijf~Iq+0x!^(U+#=9NiP@iX)LKzZ!p!L!+=&-SL`|@K0Zt(+5s^7Y|kl1NuAC z^^#70k0XPtSq<38$tI2E$x#{#g`y#|TqXm{&2;BxR8kO?{tSVx3n$@*iP*%V(y8d! zVrVJpC?Q(-dyQ+#NfF|8w6#O4Pth4w$|4Qz+kX%I&_=gSA~x{IXPv5jK>wG9f{3Kr2q0=OmlbRh-BV4#O-NV8$# z4#Qx;jLnzrfg~o$OC*zF<_}THB|$Yf>Dj}5198Ycv{yT&1i~tV33Vn;);%Q@=i|oLH)}5J^Tmq1iC9Zd#{OGD#M_xRJSwp=umEdQ6GgX_~kgM-BF+Nv??fFwrGNyee6XW=$o`= z!@jy;o^XulJNeVpVnS65jT$scu3W95Py6bl9LkHyCx_alWEs8|^_-3AD~)2YJQ#POpMiFh`XadU@Cbw!)tsJ?O&P|1B-{U}^TwhOk1gjH zdj8^|Sv}Nj<~`mGxfR`}c>s0ZwPU>#siigwkKwNJ*!F8LrqqqpUXX+ER7D6ioO{|~sZ%HNu_At$Rq05^hT>@i_Pwe;<;JNzxiY7{HxmcxFnh{GxXX=bfIy#F~)u&_cU3bahYoBc$UiUo0Z)K zZrx~tZ0+-sZcg_#|FSeFavL&yzaH?cQ!cN8K5# z8~VMd$(`|*p$n=rT!^Bl7owy|eq_1P&cHiZuz!Iw(xCL=ZQP{es-M=i-Upp5=aH39 zaCuE2MeEL*wikA>$ieC*Rgmm&{moREMS9@WRKc86TA!+;-Nu+MS8lF8QZO@t>aj@5d`I4dM%FLe~rG0$kf280zdD-<3(}6{5!@ za2Q52PcX~xhnPwjmWTZ4K)3ea-f-$GeC05%e+2O7U&Hp4n|0)V#*L7FlTYxjH`mXS ziR2^I(eTHb$cot`$ymt-@5qyG1JOf7G)3l*)ti%#wK>p(e>p3jX)7b7%z?6=xtuU5 zFdiOXml|J}d{a2YLTe%UGZ|T)xT0slLPD@CI5>T(OJV??um7lfyiy`mQW5Qmm07Yb zQOqB^Nh}^@WGcnj7@uQYB3UqS0mn&3|6Z9&J{nDA{;Z<_(v$DRtHQSq&Y6nmn^X~) zP)9|H#W3Ceygb{YTO0LtGcZ6m0!$=LZ?>xeODSR@>iz((-s0;76UlC1CPQRtp`ox~ zO>Ajd@berLCCaE*cCsChE=v>Yys{Ki-dHyj7sbR8YET0PagLZn#V%lx;3naRVLt-6 zrr~_kp?HuC<0zL>t>eTGl-&%1T%K!lpva9=qi@4wO@&WSQ_LdG?}oBqt!??n(2NE# zx%KROj_fH$)cJIQ8RLao=O_qG~+^zZW;nlzGFre;kj+&r%jwe^xw?s{)w z<==kMct5D9CM=AKVfyAh0b0y<07V+)8R}n#-rh}vxKy};f4K4XEI30Ju$O5^MiLFF zR>Y7o!s?^zcEbMU6MzwdMLg*7qhreew6BQv#%bn35Mg>!0;_1Ya@Omlc|ps6I=8P$ zx+y`-B|lhx*fe%G>u&F9>r5KEvQ8F#Vd>CyQ3hzJO`nh^?tY6CwdP8ng++xgmJpYh zQduywJy!R+cBUeQi_1I+iz%%Wdj5y(`cXKPaq>dXoZ@fnj$@xgnWC7G5*}n*j@m8<+T1)P+dm@pH`!WpwCKPcX zPHWy^rn~sl-@gehx-AK0FdBHunAqVR!WKR7xlbudT0WcB{ zYM^ysphtIwTg6ktq`Fr@v=WB=v4a8lbWb&UKt0jw6I>AN{hP8`GKU@jIkaAq95Bf z4DMXO2L@XzZo=ddr6F*ykb8O<)&uJ5gI{mlBjH3#=bpv`PZ`HpIHx^3mbq--e*7K9$FgF`#y5MXegSPdzNz!F@=0fr;XDF7_dq}{a zl{DVfS0=e@;#lBU9&UrSaw<@p6s=)5@9nr~@i)3!FzD0-_m)UP?p2m>24|gK9Syq2 z$0xur`JXfK4F%)~#eoqx>oLJh}n?Bycz z8Cr)i-r*D2u*r z8*=xwVy}=cDpehFhFThFs>Hm{!6JXWZV4V=5IjdtEdCcx@XIxi zZ9P>KGC=qfjVR~_*miQRR5#OX3jXB&a>PC)_JdtGYI<8F$Nay0P|05nnYmnwbc2(X zV{_BIui1V=yEQl+e_2O$OD9GH{IROdHN>m)G%cZAQ&2!{ceglyB-)#i>vwJsvfRzvEW2XjL{7nt9D zGU^SHsYI|(yR&XgmC-}Q?*nH;{^MwN94>6O*SVbkRXzZZqF~giK>yU-V{}9u^ypUpqJsXh4IDsP)QTa6#1F&axMN;sX?}- znU|o|ZZ~vF zt^MntV|hmv-veIP)m z)Vgy*{+bHHA*=wjRm6D~hN;^{c+au)sI4$h&g5Kr?U@@x1gq{!ai$b}Jn|F|d(@em z(ycuO8OqsXA4W1^XOCegHs2_vEFD5C%Qa+(|2wtX1GwPxQ@aTnkEClg`YPM3*mT_+ z63lxW7xsmt2|7LjP?5w{(870-%lQ#d)(&w$82# z+NU@rIy(3*+F|X&5JjtSj=?@FAd(169QLnlSd{HQY0UuD9hWGG}PoXr=+NiAwQ0@nN;Y;CYoiyQ@UE-l#0k z{6#Ovn_~RiqS3xn*(lqY8B1kUv)`+}H4}Th=Gm)q1H(e_r)XJ!uQ*ewUELSEUbZbk z_1)^Sgg&Q#PWO#!{#sc;YziSz)>3JQf6XM8B{!?Eq^w8iwAeEtU-JkbVbeE|QmdJ= zoTJ&7G$RqVa!yv|EPPvb_(CWu(g9F80WD)>;OdQ2d0w+DbzS;_Wc{5FPR5Cr$%u*T z3D3~L$@KCX<;Cu#gicLx1vY7$@lV{LR`yOz`a>&yX#+y3<<3?-BUHOr-Rb)h~^C0OgK;m;wOOsVgUt+Tey>w+X z`OjOQl2DuMLxl7az|6%wk7pZye~3oA->aRKiVZ2pY0p|rX7}rQHxaC<=FFCMy_9?@ z^D^jzdQo;caS-@Im`$$WmNlzE)&F0o@1J8C?R5rL%m!v0>8lQks4k8Nj@kWT-*=s_ zH72=)o9)T&pE2CPCX%{8X{`wwx~=NuI%Pw%zjzcwoH)k9x@evL@%h82f?<$>4{Gg2 z?sy%=PU-CNq346qy*y{{WYDHkwdE&RitgDaNAiOsCc{NH^CTa4Zsg9y)7*fjD%DDO zVk^J7zkdUU`=Sw>H_r<;jY#FqhU6CMdP0e7M%LwnGr=-2r)=V@fCaU7@448@= z(=)z!Cmi*i{QUyLCc?yW*e!b~G~;GxS#4E3&1>4yLt2jfbqM?oe*k;luKdw<;)BOM zhe`8Z>{i0>f?`@E4zd3=Bt>fno}~@hwbt7k=s)JQ7S=mbz7*UkQrk6U&0INH$p^Mj z&GXEar?D{!fOK{h{#KvMq2gADzwlA$aUr9TyyvdC!wR_Q<_J#Y<^6ih7&Qjn-NV7L zs;a1~kqioE3P<|cYDS#POG`*oNc;=zg`JX`AR>N0I->qv)thbR$ z!6~?$mNcQS#a{BRkBiL&?JXuV)-3xAxskAxoV#+zhj=rFc=%*`Vm*vg#mlgGV=KUMjs3}al|40+@ zAuYyH?~}@^53Av#SU&sX?WUPp&d1Xm#C>sI&IkQ*4ZuIqpPAA>=DWyh(D?>s&3Tsv z4L;iv7IsTlYkWqdDUXKWLjnG4Td{*U>FRPG_)6djpy!~T@h^l&V}w%%5&Rxmm;L<& zIPaB5mg4#<9w3>g^q)?u2H`(xb*6R=F7pHQHg-+IRi6{&)hH}vdyRdo?h6F95mY|` z4ryK+l8{ACks-@Grc+Ua6-N}rJ2cN+4iohnv&(qp>7^eB5f5s+Mu^36e6uIwXEbL%ORw$4HUjMsrX$>?di!5CA)T#4(^KBdr-Qg2S6? zeQ?a=&Vkj-S;HF*M=lyNjuij)IM45z4^_I24f|Wl!(u7xEd&M)cg%6_j;Tapp|=~* z^A=1o5`UpX?sph=odU!O`OE;gM=u6I>_0*`@;Wy8x4(GKMf}V42(r@jQ>VO!;w-vm zT{{$EKcR`A)iDWH9z04p%Mi6Id7zAqp7nJKfrE_ITjD=hnST`^R*)aHSK=$`kni5Y zoZx+>FgibV87oD%K>|1kO>sp^-0gbQVqbTIwxDA~qs~F`G;SfkE-cA&xlR;tD9Mzm z$mYN54VnRb6r2$==?oruGy95$nR`cG=0n8JFw={eK7J|`NXF)IYy4c`x_Bz+Pf4;7=DR_l`N4zZhZT9h4feN+6G^{xLjsBPh%v#={X&Ti930+|{tQ(x)Xo*yN+zlexCC(WyS zCFWc8r2v@wT5+fvLri*Fm0%6zkj^n4w%wn4QII2|ou4bo-41`wy3CJKW`B~G?$U-o zH(%bBBhg0F?I~VZMt`y#b(tUbg%kTon$`VZ(30>q!BH=od}KSX^A8_RMJnG}8--)eFjUd=^yfOtIuyK8GdSQFdx zJMa7$pou~|Ez6Vue8fdD@j0=O<_9r0HV=i_hn}4;0C#UvGX`5Q6BLZU6GtOGuatjJ zj>jbE#yYzvE_wF^$k3xn?(v=__8~hJQhVDDt3ZUl-D=nS?WiY1og<%`%IVeCuB0qk zY)X7LO3D65LV>2BjM>@b{KT#AApKcgbSe3kUh-Y|s_zdSox7~yZ7}j>XZtrVFJPnL zJ_BDlZOb*>*Yi3cI>}x8yZ8hl-F62e1>` zETjqe_uumWo8PD$0SUmt>tHm-mqn@ETHl1Jr)T$Dk25iYlPEF1^3X(x`&PdKVr)CcK&RsL}z4y)BwcgBI?~nIK zb#YGBNmaXc?fu&UQ?Y3g)O!m4iHSC{$>KYu>kKS+e8I~tZbzaS&0|i_4t}3Bg`5RY z%=G1N3d0OWW-H~>&&M^5=;dPEtzxCZeq_CHd|VV#kRzm|UZ8x~F1Ci4lKcYKdJ{az zEUF^enuee)P6|d#(M$4$4)Kw`3G~t|<2AS!x0uuYgg~%i40ZUuFuyS5x=csJ%8194 zqL9LhY$j);>}~b1Lo#-qm$kJO4KW95ILZqY-E=Y*TsJw{Eyvpa14fOY1qcZ*WOmZT z5kfJ!N_4MRpb=WV^zw-Sz7@acFfyXpK<(fJZYdyrSGuWcSA*aq551;CdR znP1OPbiWeuOad#d-$8R6RlY=U$CQYNBzfB7(J$vZ{3Q(I*S&l0V3B7@Q)dV6K#NCz zVRf+=8Jwp7acFaQI^gw7b;;xOOuPGOeKmBhSutSnHy%+nE8p3)?w_ig(V)RX%F*-3 z{paInlZwNiLVxp{*hNV|MfNL~#OYUB;M*moJD)jupD$hH#wUP#hhvqe6t+GVzXB+h z;7U#+4L6@1T@=z?r*xQz8#rAMbMAG8?GDV;&GBiMO$kyA2hu4Rjvh{!5eswq5`3qB;pPkh z7dkcEMWg)b(kb#I*0FcwGr2>P`|}wq9)p>jX9JwmoI~6)-+SYS`S(1$4Np@mj0`{N zbc(Rx_lx4f4(L1+@apidr524|{|(KYP4d^{L@z%cdkC? z^_2W!H|11!3`QtjyaBVKD;>r_t%Cn{!~mv6&q_;3lbV9}HzH?16X>4<$?bFlnPs0V z6hZH00yz|%?R4-OuKBHssG-QMa#F)fW5SC=%Fg#}lrOAOcYGq^4Y*0d^h<2D&TRxey z9!1}mXe?ovot^E6HZhrmbAU6oxmYRThKE#zI%tlA;V8-D_yxTnoiJ?AW+Fu~Aj~+# zN?r}ztEowti5dUIMk({FD#&YI6?KZh{Z6=MuTp%fD9^W;_c?5oyX3aq6e?(QxKYD_ zRoelX)*^0cYogWol)Pb^7L50X^c3kdP0C+}G3qp1mnwr`)lfcqjI0uIEjXOuF%l8; z#$mT@U(BPoZ0t86ZBHP41u-DNBTA#b-S%;(F>_?)E4QWPya+%Le}wE&Fdb*z^lJ+G zIc8-74Pwl<9%nm7>hFY;mC>p7(7y<}eL{I*a z-2QI1(T*rCG+2PZF`i@{w)I(WrCGg;u>muCrO{^w7glfwPGXQ~el%YoNKFDPu}&a@ zX6t(X@vo;tjvKPB@8tSjtGERHHp=TpV~Yw#AA;tnTF}q1kvPu0At;)8y9p zPnP9R+PmP##xCEN*1zAcwzbt11+eaGLbzhQdULcfil@T#F=k-cDm9fF2}$~zXq8hC z2@+0avgZtYVu{uXc$apO?%H?PhEGk|mclj2l&A)z2;a*1VQh7#y-eZKP&9*PbHhMto%raX&-5mb>E@!BFf5G?Itfb#_*qpiZ?loV0 z(HR^ULs*$BRt0aH+3Ih`E30O(mCsHr&jvsLWSu6j48*hFW&t&;EIMVq{%`ol3+zO(>aVJhAXqsP} z&nE*fZ;37Sm!;4A2w|e{lIg2`{(>0}zSXGKa9C$eE`3o?**0eFSe>5^U6CVH@toe- zhc9ov<}rBqcIR0e{6n1gHn;!=UMh2T7?5jk1w>1!znJGg)SyQAZET?nfql3a zlzK*Q&xrxB_w)A26!CYlxzVTdv(<(~b-9%klftVx@`d8asC@va5wRKlAbyl9?$#!c zkE`E?=DZi4X3JI%sQQ{ICi@H7Lnnq_sZ`5BJAQl&&%lpj`zcSrykr|!=39;NZ4dOx-CdD7$7{6d!dKH7LK7Snf^Ox1*r{dbJ@y937q0-D^UhgU( z{Z~>&=U@DS6nf99ZKj({clA1MpR-jaJFp(uuzHXk>v}R~PV61BhOQxcn4O|V{8vM> zy|3Ri7z)aN+09=$CiO6_-g`^ns1g{~8Y1p&?^dE@TeMnRJTzJ1c73W47X&uA2@!^A9ahm$Y$}N8`FKpTwLU6gH ziOtxk%ylhoo59`8HIN}~Ls} z+@tJ=P9fOd=8MWYFHA|^5dEFwBpz1+vXB&5E%oE~H{kpwoa3Sd19H;+=hxMzNVM_)c;gQ%2Eq@gWF6d8Eftx5B42Rx%nP3G@8E`xLH_=o9-;gF|? z*~AAAIbBlU7h%}5VGp(xuYZyJaggzH<5e6v3!jeeVBkGP70XN|q5E$kLnTQ;#;@9c zcx|#pHd_o0t7r=>k253}q+%z!R4W@-9ngWD$|ZwIkpvM(Hm7t2?B4h9qf; z3(Lm@sceaIT!(%3lT%hGB_A-j;dT+wYBvvhHt%inpo@&`=!yt|Lu0`r*>k4twG(9e zFxmUb-8f$c8XZ7dMe_7rO~yYfCdaws8*y|48#}>=eXrd%9(k1P4N#fj9p+&d1LSWv zyNZNC13$E}M!S7Z5Hk!4*%it~b2-p`TE)?i(#tm;${`({=DTp?t{y4K3S`tdb-!nE z?VyGO)2CZ!Tu%>}cJt)YGq)I5uD~uf#`>#`s+xE9FxBSVa3~>!K2-h|WCId;3F3wl zAKhG(cX<@0QYti11(6_B{LqPVJx$GUPM1B(HMQ{gCQ8v>nv%O|CYVSrw4&Fxjri3z z=PP#FLi&b;6soTh8%NddW6 zlJ06qK=#eAwNr0BJszQZ^Bx}0&Ej(mmiZ;1!bRbBm@P;p;3Q6|26i?OH0pf2nL5B_ zR#R@>ue2y5KEIf3=L3}s=9|qM)BLPp<*7MB?q)n&#dI%HaNv}!Vf!LpvOf{P|D6iB z=9BXJ4d9%Q;G5~xt1}|k{@SMnk1L*V6*pdj45d!d^VVV5V4>R)VIMIWLb?8PWCONq z0(c{2rSo&E(-)Qt=kyMoX|o%gLyy@Yar7AbJB!fv%3mA!T`@YDRSbLAtw?zqYu`~b zpW?xR?-aXrwWLKt%Z_8bc zT$bGD{jEQm9%graq~U`(+kYvL(ERx|3!)R!L;R+e!wGV;TKpZob%{m4z!21d$v1TW z+z%RNCTyP-nhd14bM|rcN?Hy-#7acwo_yL|$~*P|XTh$Nx31#s?2mr}cg&z%rdclk zyka(gc`@$hr$GwA5sGzO642Z6?Nw$bFwRB-R0r)5Jr#2^6)|Vg;=;;CT&emtlc}Xc?!-8J8wwD#RuBZ=n7-1b*tqR&# zzb~ZjEPeKqwXWT20sPdGP@lw~;D65f*CQ)&K>3bVF!qv4t-6+Z78m`C%=Ad7XKM8# zC{=SzG--ntMAY*E*vnKRh0W{PdGmiFnW zsAg6@;kC(DiNmK%C&PVVbKhCE`&RYAeIQNs#h0lcNCOk4w=S*^6~{(1Iu$HW5%tv; zRI{ax7S6(%Wx38a_8?1``ZDIEqPLq1fGYU%8bGw<+OxhXeA5K_`9i_JNE}bgi1s^O z_$#;B`aFbJA8$H#vcQRRZ09DL6Mlx|PqS6=A45K!%ednS?Qz%> zWvByQdGh3JX~&8WFfF$-g!Uc;$qWM~F}X_rN#h2G34rQm|5zu)l{x1kdeIIj+Y$7X zKu|4S-*?jiuBE{P`B>?NBK{7a@o_Pt9&)w;58*YO8aCJKdOY7!S2=#VgopBROniNH z89cuN$XKy08ZX(hq)VNQ?Z}jUoDKrz=u@+!-M!ZwgtPh6nIKi(d`!Rl;+3B$IHfO(ec!mpd zt33#R|1R_t>?p<(xy9QLeEX8XU$_1DHS6jT#JF!Wt&8|Yqq|+pn%=u4nHahaQ7gf) z1O@Nx>;g&@_PI<~b>h)}E=y>} zgMeGMk{fy@#BqJ%W%2OHEPDqciQ+2DfEF(X#i2H8 zw^jjX4#-%PGXSEG0g&AS-~wvn6J$dBBuSBNicuT*uw8!}4&?ZgX@(Gi%=U0j;Q8&Be;keOpTcOzs+MGI3;+~T7 z@-n9OZ^PrF>OE=gUn~plou9va0CLQdzqOxhINm5|2YU2!9-=_kU$(JLiJtZ4fxEa(mA&VwAef0d)P!CGA{9kP6;!jms*$s?6|RX8ZB2Rm znz!j){M#bP?h(<&Ie6iYz)U5&E2>HR7-ruUIN8;TEVecp#Z8-Bf}pmj=^rxV>$OX} z6lcfK=4!EQLfcm!s_~<17e7u^-5|X1?Dq_Fhi6cz(byIH`ETtS(w7G1h$lV^Ale16 zV9INcS~GVgurJ4;tQjhm1#Hw+41*VmM!=VbUxJPfP|cVP5+;D6{`IJuL7QLfmWOKo zO0p)u(yZ_AJHEg;sFdV8$tXg&}{7ci+Q zb7Vj7%E6U&f?2a(_tzRj*;qx0B)3#xKcsuuCi_4uc{o}24RH{mums@xx|Tv#psESG4!QQ@@ab&7;19J{UekhPt)EOhT@rxDO|+9-Y4 zSrn_?D2?Acptv}vb}#M6c>v;J?Z@ix^bkyy3d$H&q)EpRb~p|{IrYQgh{eY(fR}m& zaJV0ORu@6IP6$BQ>peu+f4dY@I)>^VfF5llqfh{u6}@ys9h&3!|GO(?#;u`Cs}Nv$ zbu?peZ5c^@wyt!b<#RI;9W1u`8N0Wg)0pH+9nLhNFXzy!NzyFMnh8BwR60gPjpy3S zO`roHl*ntTe=iSJQkWod_(xue`?xeydN{bwB}AU*6jo-zO>MGxNMV;vT*ZVjEtGlJ53A8ci#5ze z?)osOUH$pGJctYYKnEf7BZDK3sUbAse=MGxC~RMzca)P(a{C^yJtMaYhD22!I& zzgihU__Q+6JQ5X1BguCF!wC;>ot8p}@ux$-Ox7Jf7*c29k*lAVo?WAPn%P>bmH{|5 zM2AN+G$}e9CsS+a#F@!)Iqvn%KyO*Bi3L5qL-`umcyzuK+>^nI5v)%;WbJW0*O9se zxuXSv`D@fa8p%~L-%mE*rzU#(ZE&*~AiqzjtpbJj>;p?1ly2ws74FG>0TgohqbJc3 zT!wyZwtg$GE^^yY3;E}P#vAlJ3eC*rth%H5@7Q1ue2w<0(=^H;PQqkHhUgC+V!yi< zsU0L@!YMiiE%-G@43zI|XF4JljZ|M(Qt>bJmz{RF6FifNg4eQ@4fJG9#iF&seu4`h z?D{Zc#!{O&nG2A#S!i48;^K9-&x&kO9?VV8iRbn7`QnfzN_V21STs118q%&R)j=!j zV2g3{g{P4@^B6fbzp-rVG}ax(7ZJ}W>AAvGHqsuMd?B!)rBy2VL_-=ET877AFx9~B zqP%K(IP&Y$PnI#XFh5~PT4&zs^GUh!#3g#aBbkbfoX2wF#xn-&3Y$3ag+_e6>+MPT z!;@xd4}~vjLp6jgav+NEYzq;~W^^Lx6*p)@_d3mLrcmDFs;anCX*r8JiYg^sH)Jv= zkJ%M*V$_#n%5~_npL`~Yaw@GZSdj@AW5&O8b%a^pS<%+h*iZfmWajg~h!bOJdrf3H zvfF}X&`2OX&bfA&W!F@idV|GI;zly{>Yp(0l}!9!mFp(0nS!=NDoy&SmGiHNZs%H9 z@x95eX28WAG)|0*-sVG4L^`a?q~~eZl#r*P=if~}P)LKYWwe)y(5*HMJ7RNN7`RBY zS`>#`sCLfO-Y0c4yhTVyfG`X-qBiuuHUF5p zJSFLe9WyrRYhzM)uqPE-NBO0pvOElQ)zb7QdVrWkgXDhWJxEhVUz^uRimMNyCZDsA z=zMN1M5_F{+|v-PgP8p$NELwSn~n7WZU!s~)hyB$3p7=)89Q;tZaCp*OH?);=%f3xwCdA3?B?Ed8F{(eStj4EUCi}3vG%1zqJ zUN5t}l(GZ=tLgna?KkEnV5ZYr01a5F+CO0Q1S~~5z31Jr;reryF?UCt&gwH{5rx_Tq&+d1^-v}7B|sHRe9PV8a-Fo1NqG}vTPG2| zxOXrwgd{CvA;3lJ3xjWYy7u3_k-=iAqnNDTDBIKDMBq-qXNMRJR1Ni0M&n@t{u!Cl zBoctgKk@BT*^x!7(>KqefoY@x>HvTq)JZ<0dE{X<^o;XepSXsU7_Iq94WGTmlR)T^ z`VL=V{tNx;=E5g&Te{^mPUiKbOZ2>(Q&WSnS_LU36Wsf(hFJ3zhHUw&j1nuKf_#M0 z)4MCR6Q{%*)jTsJ>x>|>-LmNq?+zITw#ZzZ*lu{LO)|M3F@;=$hH{&`dLGZZ=X_C6qKab_2*0@7EPIna6WdhSE=(&HXg)bQu_AL%Z??zd*$e2Q!D&CWjHsc8 zj)zBhVf_~8RnXVtJXgVC{vKyFxFeXG@gY#9XRPRrdy(uA`)QA+$38X%JKZxqBKnm z6_!LvHK|dU^d?+21Hsq=azI*d2;aRc9wtkxlnHa;@P8fTr83cNlP0;SWEWrQy(QR} zwW`%fBa9|1njoh*QdwKuPcOz$Oj!v+tOoAZ3yN-ch!G{^%8jJ=_dSm-*8$wBJaU6J z2E!74fgCasZmRE{mR(WAsL#2VAc>xb1;5Ksp{iT5Ag)y@%RA$ZK`pc|lSwd_f(rlXSerPC(~~mD{>xIsR_y zDg1MTk6RSUl(GEm2b+!t_eX?%tIX#7DMsH;?;HC=Ke?`|0&)tla)^KBlUD)5>|wo& zK@%w<_uM{p=ClG*(pwjsBe<*M*IRZ0NX2Y(?#UNMQ4iL(2HY@mi@EFv99+MXxm!Yg zFk7$O7!FpITO89}Xk)N>uHGl8AnDn*M{UhR2jF-(qjw9Z+3E35WKC-5}OWOk|-9`7Ew9;RC)XYw!pX|(qqvk_n7k?zHTI2 zE0zWpYY4YG^=)K0^_IqZNBUdev(zoFGS9F0^sguViFjznUX^f;3G@Re4}qQny=a0C znbHVrs5R%lGy1u8gv&cQSO}}b}pdOsB4-+Z(vy@-z=?luqPTVtl7x>&P%bm!z zC0?aw(B$k%?Cs{jA1zZh8SAWFu`^W{>Ndre)5THvR+*~p-6s!^aG$!O=ztAkeJQY8p*_KGMBlf+*f|D zVl_w-1dtzVIToM#Z}|iLFKF}MCCt=9e5E8$md-b{j@enAJUWo@N-AFEYRPo`Zhj2u z5Bn(tsrwC^KtM^uPWIiTLC21hor_Amb8{X>jNMd?mK)5VuW)m}ZuG=T>s^}S6nlLs zL#&A7S(JuI_cnh=BO>*|dNM!cskwLhZb zMDAs!e|~1;6An9&idqlI7&z8kQZ!_?KyaPz3HN`sli;u5$do&lE?`ROgF-(|UowR9zaV4gJMn6aV2nTq&^cL5 zM3dKH>r%btqnsbwk-UNA+3iHJ*#5?9vmM`K&E;v_P-^?_uoqsev3?chj0U-EJ2-GtiGXnBw|?V zp*7jWpG@c}^z(w4r6(mn#UF@k?9y`)W^#BOS~w<)`yrI~DtHoZnZF?z$rBF>BthK9x7tH!x`f`K0$Fd-v>l_$)uvX z+2UJKfymP22~6f3^sCM269D^LfyGRD@1=+{^`A-EqVRBh4YS(NnUcs0O&G%%#`ZLq z=K}Ndyv--(dlsAqKH0LM+WlG3jwY@JfQ5fC>*}k8;+7O4M-6llpOY%X^J&{0`%E6iMB>1X=i6#*7_CQ%qPqg|2fFEn2$iUrQ8@ngKAG!UXls2=DBNKh6Xr z-Ho4T#c7;Ce_u-r>DB5iJHDTM{BiTwdVT>h{5MGl9QA&0$%P6|Jk?n0oO1+n)SCCA zv^SXhS~E3yoni5Vz5x`OeK)1>X18=WO(54tmir9Rzx*8=bZ8<^-3N2y;XR3EuL^z( z%1a7B!ocV}%i{3Wnk>R`5z;Dmek6oLAKi;A(E?TkT8g&73l1Xwul3U_LGyp4e8fu% z=)qTbeV86aAeQ;2U*2nA)|0g+DQ@t3II$b?cN3z7CsCT1*DRmfLljlc8ME53c?mi7 z6|2qB3u#yyaBsJGWC{KXvugs>=N$vWmDu95d&UT-Jv^~)(s~Z6O5HGXrg9153_?v8 z=)U){5DiPJCNnQQm;`j0rI*a{suoYT}q{@@1?_sqI z99-dXAN>hBjQdr3)FSui_rb})a~ZS_A1S|HgaIhAQ%2vQjHL^C4IM^!c#G3Q^UHFO zf$~_&4T2YCH(Y^OK8are-ceEvprjB!Q%@Q+?>{%M|1-1uf6w`siXcu4UPTVHPujY- z-NA;$?eO%dcU^bb-7{~8pKZh&^hF5$vb)l&W;Z95dN(A9#ky;xb%=kPAJlxPTWCG) zBj}P|ByCPYwFUft7K?8{Dy#F}sJG^w_~q+=)Yl+U3jGe!=K18EcT*Qb_(1EumC;eW z5`J&7)5?nh{kQf?D$zFKlI=W%A$)Hc{RzhIKMjU{uV5(QbG~swPLY@{)vrlegDH{! zZ0Y_pF{5tC#Ug3hnF9`PhY~G_`(l&&E3Eib@KwB0uJ!(3Wai31e13KvX^58uu_Elz z4<(|GsgIGH-TkcJYdU#Bq(cfh+|6UL!neEMZHgf8nb`_im!mrFoK%A z!NS50n08Y%Gp%VrFAUGXLE3^jh2{>QX)xO0kAnn!KA;usef3L1Xu)D9%}jC=#zC8) zPUUGQkLFQeXSSr{_i%++$|=9pe#6-INX)||OIe|iA`}M(i3Rc}E!p3vi=Y~)ittJ^ z&1kZf9DhnQl|xI!;Bl9PsW_VpGrfd$0DrnnU}A_N&{4@%tvAMjoOO$6y*WA_zwLiK$=bkByCQb zD|s8}XQ9Im^Oo*M05bO-5=v{n*8^1+PRh0g8$CCIcV* zFDQ{0T$Dc+EG3H9jbHZ}X;Da%J|h#Q^JI|xQo;Dloc@M3i zvLnsn*;#B2sJE~11^TQrc%+%}qRq$QO|%Jw*2&gTYQwLTz=+5K(DOgmq~o%sDL}s) zhrL&I>UB2@r~Vyj=4)cbRThAj>}d)pB>&<5y`_MDEI|s4H=tL8c!4nox`F;wr9pz! g)<0U)X3@Xhy5HAl818lD>`n0h%l{K&0J$9f7q+5y+yDRo 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 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 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 217/401] 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 218/401] 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 219/401] 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 220/401] 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 221/401] 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 222/401] 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 223/401] 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 224/401] 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 225/401] 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 226/401] 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 227/401] 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 228/401] 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 229/401] 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()) From 78b4aade088a52305a160c68fa99b6ad38a4b133 Mon Sep 17 00:00:00 2001 From: milkmix Date: Wed, 3 Oct 2018 17:55:08 +0200 Subject: [PATCH 230/401] corrected typos and unused imports --- misp_modules/modules/export_mod/osqueryexport.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/misp_modules/modules/export_mod/osqueryexport.py b/misp_modules/modules/export_mod/osqueryexport.py index 11c253d..a1535d8 100755 --- a/misp_modules/modules/export_mod/osqueryexport.py +++ b/misp_modules/modules/export_mod/osqueryexport.py @@ -5,10 +5,8 @@ Source: https://github.com/0xmilkmix/misp-modules/blob/master/misp_modules/modul import base64 import json -import csv import re - misperrors = {"error": "Error"} types_to_use = ['regkey', 'regkey|value', 'mutex', 'windows-service-displayname', 'windows-scheduled-task', 'yara'] @@ -24,7 +22,7 @@ outputFileExtension = 'conf' responseType = 'application/txt' -moduleinfo = {'version': '0.1', 'author': 'Julien Bachmann, Hacknowledge', +moduleinfo = {'version': '1.0', 'author': 'Julien Bachmann, Hacknowledge', 'description': 'OSQuery query export module', 'module-type': ['export']} From 1d530a7fa6a600ff230ef4ab91f9d009e29ab3c4 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 18 Oct 2018 14:44:57 +0200 Subject: [PATCH 231/401] new: First version of a yara rule creation expansion module --- misp_modules/modules/expansion/__init__.py | 2 +- misp_modules/modules/expansion/yara_query.py | 44 ++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 misp_modules/modules/expansion/yara_query.py diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index fce9343..f1c6d7a 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', 'vulners'] +__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', 'yara_query'] diff --git a/misp_modules/modules/expansion/yara_query.py b/misp_modules/modules/expansion/yara_query.py new file mode 100644 index 0000000..89d2e65 --- /dev/null +++ b/misp_modules/modules/expansion/yara_query.py @@ -0,0 +1,44 @@ +import json +import sys + +misperrors = {'error': 'Error'} +moduleinfo = {'version': '1', 'author': 'Christian STUDER', + 'description': 'Yara export for hashes.', + 'module-type': ['expansion', 'hover'], + 'require_standard_format': True} +moduleconfig = [] +mispattributes = {'input': ['md5', 'sha1', 'sha256', 'filename|md5', 'filename|sha1', 'filename|sha256'], 'output': ['yara rule']} + +def hash_cond(hashtype, hashvalue): + condition = 'hash.{}(0, filesize) == {}'.format(hashtype, hashvalue.lower()) + return condition, 'hash' + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + del request['module'] + if 'event_id' in request: + del request['event_id'] + uuid = request.pop('attribute_uuid') if 'attribute_uuid' in request else None + rules = [] + types = [] + for attribute_type, value in request.items(): + if 'filename' in attribute_type: + _, attribute_type = attribute_type.split('|') + _, value = value.split('|') + condition, required_module = hash_cond(attribute_type, value) + condition = '\r\n\t\t'.join([condition]) + import_section = '\r\n'.join(['import "{}"'.format(required_module)]) + rule_start = 'rule %s {' % uuid if uuid else 'rule {' + condition = '\tcondition:\r\n\t\t{}'.format(condition) + rules.append('\r\n'.join([rule_start, condition, '}'])) + types.append('yara') + return {'results': [{'types': [t], 'values': [v]} for t, v in zip(types, rules)]} + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From d08962afd27f3045e63bf27475e595dd67f5ee16 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Thu, 25 Oct 2018 00:34:44 +0900 Subject: [PATCH 232/401] chg: [docs] Added some missing dependencies and instructions for virtualenv deployment --- README.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f284e68..32ca538 100644 --- a/README.md +++ b/README.md @@ -75,17 +75,35 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [ThreatAnalyzer](misp_modules/modules/import_mod/threatanalyzer_import.py) - An import module to process ThreatAnalyzer archive.zip/analysis.json sandbox exports. * [VMRay](misp_modules/modules/import_mod/vmray_import.py) - An import module to process VMRay export. +## How to install and start MISP modules in a Python virtualenv? + +~~~~bash +sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick +sudo -u www-data virtualenv -p python3 /var/www/MISP/venv +cd /usr/local/src/ +sudo git clone https://github.com/MISP/misp-modules.git +cd misp-modules +sudo -u www-data /var/www/MISP/venv/bin/pip install -I -r REQUIREMENTS +sudo -u www-data /var/www/MISP/venv/bin/pip install . +sudo apt install ruby-pygments.rb -y +sudo gem install asciidoctor-pdf --pre +sudo sed -i -e '$i \sudo -u www-data /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s > /tmp/misp-modules_rc.local.log &\n' /etc/rc.local +/var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules +~~~~ + ## How to install and start MISP modules? ~~~~bash -sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev +sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick cd /usr/local/src/ sudo git clone https://github.com/MISP/misp-modules.git cd misp-modules sudo pip3 install -I -r REQUIREMENTS sudo pip3 install -I . -sudo vi /etc/rc.local, add this line: `sudo -u www-data misp-modules -s &` -misp-modules #to start the modules +sudo apt install ruby-pygments.rb -y +sudo gem install asciidoctor-pdf --pre +sudo sed -i -e '$i \sudo -u www-data /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s > /tmp/misp-modules_rc.local.log &\n' /etc/rc.local +/var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules ~~~~ ## How to add your own MISP modules? From e8761c1664f30ea0522dc1281aace5ab2558bad9 Mon Sep 17 00:00:00 2001 From: milkmix Date: Thu, 25 Oct 2018 21:28:46 +0200 Subject: [PATCH 233/401] super simple support for mutexes through winbaseobj in osquery 3.3 --- misp_modules/modules/export_mod/osqueryexport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/export_mod/osqueryexport.py b/misp_modules/modules/export_mod/osqueryexport.py index a1535d8..084762e 100755 --- a/misp_modules/modules/export_mod/osqueryexport.py +++ b/misp_modules/modules/export_mod/osqueryexport.py @@ -42,7 +42,7 @@ def handle_regkeyvalue(value): return 'SELECT * FROM registry WHERE path LIKE \'%s\' AND data LIKE \'%s\';' % (key, value) def handle_mutex(value): - return 'not implemented yet' + return 'SELECT * FROM winbaseobj WHERE object_name LIKE \'%s\';' % value def handle_service(value): return 'SELECT * FROM services WHERE display_name LIKE \'%s\' OR name like \'%s\';' % (value, value) From 8c9c70926d30339cc37efbc545384b0618fc22ce Mon Sep 17 00:00:00 2001 From: milkmix Date: Thu, 25 Oct 2018 21:35:21 +0200 Subject: [PATCH 234/401] added basic documentation --- doc/export_mod/osqueryexport.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 doc/export_mod/osqueryexport.json diff --git a/doc/export_mod/osqueryexport.json b/doc/export_mod/osqueryexport.json new file mode 100644 index 0000000..c5090a8 --- /dev/null +++ b/doc/export_mod/osqueryexport.json @@ -0,0 +1,8 @@ +{ + "description": "OSQuery export of a MISP event.", + "requirements": [], + "features": "This module export an event as osquery queries that can be used in packs or in fleet management solution like Kolide.", + "references": [], + "input": "MISP Event attributes", + "output": "osquery SQL queries" +} From 37e3d091024690a17289550d39586b9ac59fec80 Mon Sep 17 00:00:00 2001 From: milkmix Date: Thu, 25 Oct 2018 21:54:25 +0200 Subject: [PATCH 235/401] documentation for export module --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 32ca538..66ca56f 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,8 @@ Create your module in [misp_modules/modules/expansion/](misp_modules/modules/exp Don't forget to return an error key and value if an error is raised to propagate it to the MISP user-interface. +Your module's script name should also be added in the `__all__` list of `/__init__.py` in order for it to be loaded. + ~~~python ... # Checking for required value @@ -207,6 +209,19 @@ def handler(q=False): codecs.encode(src, "rot-13")} ~~~ +#### export module + +For an export module, the `request["data"]` object corresponds to a list of events (dictionaries) to handle. + +Iterating over events attributes is performed using their `Attribute` key. + +~~~python +... +for event in request["data"]: + for attribute in event["Attribute"]: + # do stuff w/ attribute['type'], attribute['value'], ... +... + ### Returning Binary Data If you want to return a file or other data you need to add a data attribute. From 53ab8a0a2fd40e8aa24eb5aec49a217578033859 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Fri, 26 Oct 2018 08:26:58 +0200 Subject: [PATCH 236/401] chg: [documentation] generated --- doc/documentation.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/documentation.md b/doc/documentation.md index 7be5f29..a89ac63 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -304,6 +304,18 @@ Lite export of a MISP event. ----- +#### [osqueryexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/osqueryexport.py) + +OSQuery export of a MISP event. +- **features**: +>This module export an event as osquery queries that can be used in packs or in fleet management solution like Kolide. +- **input**: +>MISP Event attributes +- **output**: +>osquery SQL queries + +----- + #### [pdfexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/pdfexport.py) Simple export of a MISP event to PDF. From 85061a0a9553ab3938f470a82b6743081434a3e1 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Fri, 26 Oct 2018 08:41:56 +0200 Subject: [PATCH 237/401] add: [documentation] osquery logo --- doc/export_mod/osqueryexport.json | 3 ++- doc/logos/osquery.png | Bin 0 -> 1659 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 doc/logos/osquery.png diff --git a/doc/export_mod/osqueryexport.json b/doc/export_mod/osqueryexport.json index c5090a8..6543cb1 100644 --- a/doc/export_mod/osqueryexport.json +++ b/doc/export_mod/osqueryexport.json @@ -4,5 +4,6 @@ "features": "This module export an event as osquery queries that can be used in packs or in fleet management solution like Kolide.", "references": [], "input": "MISP Event attributes", - "output": "osquery SQL queries" + "output": "osquery SQL queries", + "logo": "logos/osquery.png" } diff --git a/doc/logos/osquery.png b/doc/logos/osquery.png new file mode 100644 index 0000000000000000000000000000000000000000..2e4320ee79f6ee297fd288bba122fa75a71a570c GIT binary patch literal 1659 zcmZ{le>f9(9LK+oj1_G~)P=F!njg_GMy9YCTWmHqNzAX4{7ShIi%hf_iQVe*TjWOa zyI&*@&oLcxE0v$^X^KUgx}%bvr+eyOXp{XDPd{k-3QzAsWd30lx?Pym3IyBm(W zrXy>|(okLdSt2ZVO~xpPz40oVZa~vbsZ>@7mH}`Az!3l!09=8FI{*#tL&48C zPKMvMKM-`Y?*1$3n?DA{0fB4)taSBA{){p<`+RrLpoIltd~!x(J)Y(rdEsIa#fOD9 zXKdCzWN1XNFbj`}Of|EhCb6@PO?@zS9(V6`+uJ4}5J7Z$&NhRCk3_RgjUpUAh{qE& zRw*0m#Q=B*hny@a>PA2P5C*`yqwY8-@5G|{U$ht!jDE&go|PZe{zN#X>uC$ud|iB5 zrw$`wyk4oyws9wo+O?fd+Gn>ZDM`USPn=3F-FNo7AiAHBQ*v{gh|Bz*sJqAHStd1u zr$M(eMb->Q3N!x$x68eak(?)_ey-UEznXHM#y3FpPM3J6aotNzjM?3aR*<>`SCi$%fDP^Bi8~LZ-rj~--k1f=_65n@VS&Updf8VV#t zZVX=Vz?g!3)GJYWdex4+?NWQVK&^VrGe%f9yCF8OBvdZ{=s#ySles*4x^Y8{;-#fz z@ggsyAYda7Hql?|d75uFdu<}S_lF?G8+}DTB3amCNXYK^cQ@1F>z}FK4_}(AYYX0?gEB>C zFQ^A8-$|dNZW#;8Y5cUx*F)62rFM#D=ke|MKvfulc~wDXo-3Y6`(?4F-a*kP z=g8g;9sD%B3(_kd$O#yzQ#U-<*6i5#9gN+kuNVoetcuu#J-1x12eXE;{cw-R@L9v-e&3IrUBs+tZ>x&8wW#3AH`!OJsPps3`9AKk7dObuXm-Bu@BS zbXiV$F-DiY;af>YIR!b@y;5B=YrLmb=ksdRLi6Orj?1GX8wKIX^p;dTH_L%ZKT&Qq`BN6~m43Vy)_6T9;LIVDRcv+?RC1>X4oikVBov)4opOuSgRax&I}E|hv;Kx zQjgA#o5&8Kf5!CTAEATBEbZ>q7%6uNY0v@Bg4lg`O&=p#q2!WQ2vH<(z9g{3DE4$r zp=0Uef2yqR&2;QofLl1*NLFLJrQyPY#$48P&PdT_%WEqBE2w#-;&?@9g`<7<#*0`{|5Lsdamk!yZlO5RcZs0sjrzc*VkqMxZ^!>x15=re*hgl`vL#} literal 0 HcmV?d00001 From f246a9f0c3824a469d80d7e70e46f7319990346c Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Fri, 26 Oct 2018 08:42:30 +0200 Subject: [PATCH 238/401] chg: [documentation] osquery logo added --- doc/documentation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/documentation.md b/doc/documentation.md index a89ac63..20ee566 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -306,6 +306,8 @@ Lite export of a MISP event. #### [osqueryexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/osqueryexport.py) + + OSQuery export of a MISP event. - **features**: >This module export an event as osquery queries that can be used in packs or in fleet management solution like Kolide. From 1c10fd5e50eab118fbfef87f5d4db7006089ddb9 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 31 Oct 2018 10:21:21 +0100 Subject: [PATCH 239/401] fix: Making yara query an expansion module for single attributes atm --- misp_modules/modules/expansion/yara_query.py | 32 +++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/misp_modules/modules/expansion/yara_query.py b/misp_modules/modules/expansion/yara_query.py index 89d2e65..a071bb4 100644 --- a/misp_modules/modules/expansion/yara_query.py +++ b/misp_modules/modules/expansion/yara_query.py @@ -1,5 +1,5 @@ import json -import sys +import re misperrors = {'error': 'Error'} moduleinfo = {'version': '1', 'author': 'Christian STUDER', @@ -7,10 +7,10 @@ moduleinfo = {'version': '1', 'author': 'Christian STUDER', 'module-type': ['expansion', 'hover'], 'require_standard_format': True} moduleconfig = [] -mispattributes = {'input': ['md5', 'sha1', 'sha256', 'filename|md5', 'filename|sha1', 'filename|sha256'], 'output': ['yara rule']} +mispattributes = {'input': ['md5', 'sha1', 'sha256', 'filename|md5', 'filename|sha1', 'filename|sha256'], 'output': ['yara']} -def hash_cond(hashtype, hashvalue): - condition = 'hash.{}(0, filesize) == {}'.format(hashtype, hashvalue.lower()) +def get_hash_condition(hashtype, hashvalue): + condition = 'hash.{}(0, filesize) == "{}"'.format(hashtype, hashvalue.lower()) return condition, 'hash' def handler(q=False): @@ -21,20 +21,16 @@ def handler(q=False): if 'event_id' in request: del request['event_id'] uuid = request.pop('attribute_uuid') if 'attribute_uuid' in request else None - rules = [] - types = [] - for attribute_type, value in request.items(): - if 'filename' in attribute_type: - _, attribute_type = attribute_type.split('|') - _, value = value.split('|') - condition, required_module = hash_cond(attribute_type, value) - condition = '\r\n\t\t'.join([condition]) - import_section = '\r\n'.join(['import "{}"'.format(required_module)]) - rule_start = 'rule %s {' % uuid if uuid else 'rule {' - condition = '\tcondition:\r\n\t\t{}'.format(condition) - rules.append('\r\n'.join([rule_start, condition, '}'])) - types.append('yara') - return {'results': [{'types': [t], 'values': [v]} for t, v in zip(types, rules)]} + attribute_type, value = list(request.items())[0] + if 'filename' in attribute_type: + _, attribute_type = attribute_type.split('|') + _, value = value.split('|') + condition, required_module = get_hash_condition(attribute_type, value) + import_section = 'import "{}"'.format(required_module) + rule_start = 'import "hash" \r\nrule %s_%s {' % (attribute_type.upper(), re.sub(r'\W+', '_', uuid)) if uuid else 'import "hash"\r\nrule %s {' % attribute_type.upper() + condition = '\tcondition:\r\n\t\t{}'.format(condition) + rule = '\r\n'.join([rule_start, condition, '}']) + return {'results': [{'types': mispattributes['output'], 'values': [rule]}]} def introspection(): return mispattributes From af0870b59c164bcd9f7f3375570f3159432d431b Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 31 Oct 2018 10:35:10 +0100 Subject: [PATCH 240/401] Updated list of modules in readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 66ca56f..b8bd14d 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [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 query](misp_modules/modules/expansion/yara_query.py) - a module to create YARA rules from single hash attributes. * [YARA syntax validator](misp_modules/modules/expansion/yara_syntax_validator.py) - YARA syntax validator. ### Export modules From bb5f6fffae38acd8a348ec94282fe51a3712b4e4 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Fri, 2 Nov 2018 10:42:40 +0900 Subject: [PATCH 241/401] chg: [init] Added try/catch in case misp-modules is already running on a port, or port is in use... --- misp_modules/__init__.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/misp_modules/__init__.py b/misp_modules/__init__.py index 3bb7253..7d3c2ce 100644 --- a/misp_modules/__init__.py +++ b/misp_modules/__init__.py @@ -29,6 +29,7 @@ import fnmatch import argparse import re import datetime +import psutil import tornado.web import tornado.process @@ -241,7 +242,23 @@ def main(): service = [(r'/modules', ListModules), (r'/query', QueryModule)] application = tornado.web.Application(service) - application.listen(port, address=listen) + try: + application.listen(port, address=listen) + except Exception as e: + if e.errno == 98: + pids = psutil.pids() + for pid in pids: + p = psutil.Process(pid) + if p.name() == "misp-modules": + print("\n\n\n") + print(e) + print("\nmisp-modules is still running as PID: {}\n".format(pid)) + print("Please kill accordingly:") + print("sudo kill {}".format(pid)) + sys.exit(-1) + print(e) + print("misp-modules might still be running.") + log.info('MISP modules server started on {0} port {1}'.format(listen, port)) if args.t: log.info('MISP modules started in test-mode, quitting immediately.') From 74bf2f267874c5cf48757b2bc3a26a57389f4032 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Fri, 2 Nov 2018 10:44:46 +0900 Subject: [PATCH 242/401] chg: [tools] Added psutil as a dependency to detect misp-modules PID --- REQUIREMENTS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index 6ab46cc..0aae71f 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -25,4 +25,5 @@ yara sigmatools stix2-patterns maclookup -vulners \ No newline at end of file +vulners +psutil From d1308f9924d67a04df11ca9841517feda808be8f Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Fri, 2 Nov 2018 21:35:02 +0100 Subject: [PATCH 243/401] chg: Validating yara rules after their creation --- misp_modules/modules/expansion/yara_query.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/yara_query.py b/misp_modules/modules/expansion/yara_query.py index a071bb4..565030c 100644 --- a/misp_modules/modules/expansion/yara_query.py +++ b/misp_modules/modules/expansion/yara_query.py @@ -1,5 +1,9 @@ import json import re +try: + import yara +except (OSError, ImportError): + print("yara is missing, use 'pip3 install yara' to install it.") misperrors = {'error': 'Error'} moduleinfo = {'version': '1', 'author': 'Christian STUDER', @@ -30,7 +34,12 @@ def handler(q=False): rule_start = 'import "hash" \r\nrule %s_%s {' % (attribute_type.upper(), re.sub(r'\W+', '_', uuid)) if uuid else 'import "hash"\r\nrule %s {' % attribute_type.upper() condition = '\tcondition:\r\n\t\t{}'.format(condition) rule = '\r\n'.join([rule_start, condition, '}']) - return {'results': [{'types': mispattributes['output'], 'values': [rule]}]} + try: + yara.compile(source=rule) + except Exception as e: + misperrors['error'] = 'Syntax error: {}'.format(e) + return misperrors + return {'results': [{'types': mispattributes['output'], 'values': rule}]} def introspection(): return mispattributes From 7bafa939b07f426cde7eef121f65188e57143515 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Tue, 6 Nov 2018 00:48:36 +0900 Subject: [PATCH 244/401] new: [btc] Very simple BTC expansion chg: [req] yara-python is preferred --- REQUIREMENTS | 3 +- misp_modules/modules/expansion/__init__.py | 2 +- misp_modules/modules/expansion/btc.py | 50 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100755 misp_modules/modules/expansion/btc.py diff --git a/REQUIREMENTS b/REQUIREMENTS index 0aae71f..cfaf9ad 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -21,9 +21,10 @@ domaintools_api pygeoip bs4 oauth2 -yara +yara-python sigmatools stix2-patterns maclookup vulners psutil +blockchain diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index f1c6d7a..73abd2e 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', 'vulners', 'yara_query'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'btc', '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', 'yara_query'] diff --git a/misp_modules/modules/expansion/btc.py b/misp_modules/modules/expansion/btc.py new file mode 100755 index 0000000..da2fbe0 --- /dev/null +++ b/misp_modules/modules/expansion/btc.py @@ -0,0 +1,50 @@ +import json +import blockchain + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['btc'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Steve Clement', + 'description': 'Simple BTC expansion service to \ + get quick information from MISP attributes', + 'module-type': ['expansion', 'hover']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + if request.get('btc'): + toquery = request['btc'] + else: + return False + + try: + address = blockchain.blockexplorer.get_address(toquery) + except Exception as e: + misperrors['error'] = e + return misperrors + finalBalance = address.final_balance*(1/100000000) + totalRX = address.total_received*(1/100000000) + totalTX = address.total_sent*(1/100000000) + totalTransactions = address.n_tx + + answer = 'Current balance: \ + {} - \ + {} total received - \ + {} total sent - \ + {} transactions.\ + '.format(finalBalance, totalRX, totalTX, totalTransactions) + r = {'results': [{'types': mispattributes['output'], + 'values':[str(answer)]}]} + return r + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From b4c519beda58839e74e079b780e61e7318e855cb Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 6 Nov 2018 07:29:44 +0100 Subject: [PATCH 245/401] chg: [doc] btc module added to documentation --- doc/expansion/btc.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/expansion/btc.json diff --git a/doc/expansion/btc.json b/doc/expansion/btc.json new file mode 100644 index 0000000..3aeceab --- /dev/null +++ b/doc/expansion/btc.json @@ -0,0 +1,3 @@ +{ + "description": "An expansion hover module to get a blockchain balance from a BTC address in MISP." +} From e8f1cd68dccf257e3b20eec6abef3e1fd4b46312 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 6 Nov 2018 07:31:55 +0100 Subject: [PATCH 246/401] chg: [doc] generated documentation updated --- doc/documentation.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/documentation.md b/doc/documentation.md index 20ee566..a11bcfb 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -10,6 +10,12 @@ Query an ASN description history service (https://github.com/CIRCL/ASN-Descripti ----- +#### [btc](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/btc.py) + +An expansion hover module to get a blockchain balance from a BTC address in MISP. + +----- + #### [circl_passivedns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/circl_passivedns.py) From 815f1ec0ed36595ed4385e4813b22955aa3da732 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 6 Nov 2018 07:33:57 +0100 Subject: [PATCH 247/401] chg: [doc] btc module added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b8bd14d..5189bb0 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ### Expansion modules * [ASN History](misp_modules/modules/expansion/asn_history.py) - a hover and expansion module to expand an AS number with the ASN description and its history. +* [BTC balance](misp_modules/modules/expansion/btc.py) - An expansion hover module to get a blockchain balance from a BTC address in MISP. * [CIRCL Passive DNS](misp_modules/modules/expansion/circl_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [CIRCL Passive SSL](misp_modules/modules/expansion/circl_passivessl.py) - a hover and expansion module to expand IP addresses with the X.509 certificate seen. * [countrycode](misp_modules/modules/expansion/countrycode.py) - a hover module to tell you what country a URL belongs to. From b01cb2832388782eda577e9f9c321c0ebd2a472c Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Wed, 7 Nov 2018 14:14:39 +0100 Subject: [PATCH 248/401] initial version of a Bitcoin module --- .../modules/expansion/btc_steroids.py | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100755 misp_modules/modules/expansion/btc_steroids.py diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py new file mode 100755 index 0000000..b379a63 --- /dev/null +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -0,0 +1,185 @@ +import sys +import json +import requests +import time + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['btc'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': 'BTC expansion service to \ + get quick information from MISP attributes', + 'module-type': ['hover']} + +moduleconfig = [] + +blockchain_firstseen='https://blockchain.info/q/addressfirstseen/' +blockchain_balance='https://blockchain.info/q/addressbalance/' +blockchain_totalreceived='https://blockchain.info/q/getreceivedbyaddress/' +blockchain_all='https://blockchain.info/rawaddr/' +converter = 'https://min-api.cryptocompare.com/data/pricehistorical?fsym=BTC&tsyms=USD,EUR&ts=' +converter_rls = 'https://min-api.cryptocompare.com/stats/rate/limit' +result_text = "" +g_rate_limit = 300 +start_time = 0 +conversion_rates = {} + +def get_consumption(output=False): + req = requests.get(converter_rls) + jreq = req.json() + minute = str(jreq['Minute']['CallsLeft']['Histo']) + hour = str(jreq['Hour']['CallsLeft']['Histo']) + # Debug out for the console + print("Calls left this minute / hour: " + minute + " / " + hour) + return minute, hour + + +def convert(btc, timestamp): + global g_rate_limit + global start_time + global now + global conversion_rates + date = time.strftime('%Y-%m-%d', time.localtime(timestamp)) + # Lookup conversion rates in the cache: + if date in conversion_rates: + (usd, eur) = conversion_rates[date] + else: + # If not cached, we have to get the converion rates + # We have to be careful with rate limiting on the server side + if g_rate_limit == 300: + minute, hour = get_consumption() + g_rate_limit -= 1 + now = time.time() + delta = now - start_time + #print(g_rate_limit) + if g_rate_limit <= 10: + minute, hour = get_consumption(output=True) + if int(minute) <= 10: + #print(minute) + #get_consumption(output=True) + time.sleep(3) + else: + mprint(minute) + start_time = time.time() + g_rate_limit = int(minute) + try: + req = requests.get(converter+str(timestamp)) + jreq = req.json() + usd = jreq['BTC']['USD'] + eur = jreq['BTC']['EUR'] + # Since we have the rates, store them in the cache + conversion_rates[date] = (usd, eur) + except Exception as ex: + mprint(ex) + get_consumption(output=True) + # Actually convert and return the values + u = usd * btc + e = eur * btc + return u,e + + +def mprint(input): + # Prepare the final print + global result_text + result_text = result_text + "\n" + str(input) + + +def handler(q=False): + global result_text + global conversion_rates + start_time = time.time() + now = time.time() + if q is False: + return False + request = json.loads(q) + click = False + # This means the magnifying glass has been clicked + if request.get('persistent') == 1: + click = True + # Otherwise the attribute was only hovered over + if request.get('btc'): + btc = request['btc'] + else: + return False + + mprint("\nAddress:\t" + btc) + try: + req = requests.get(blockchain_all+btc+"?limit=50&filter=5") + jreq = req.json() + except Exception as e: + #print(e) + mprint(req.text) + sys.exit(1) + + n_tx = jreq['n_tx'] + balance = float(jreq['final_balance'] / 100000000) + rcvd = float(jreq['total_received'] / 100000000) + sent = float(jreq['total_sent'] / 100000000) + output = 'Balance:\t{0:.10f} BTC (+{1:.10f} BTC / -{2:.10f} BTC)' + mprint(output.format(balance, rcvd, sent)) + if click is False: + mprint("Transactions:\t" + str(n_tx) + "\t (previewing up to 5 most recent)") + else: + mprint("Transactions:\t" + str(n_tx)) + mprint("======================================================================================") + i = 0 + while i < n_tx: + if click is False: + req = requests.get(blockchain_all+btc+"?limit=5&offset="+str(i)+"&filter=5") + if n_tx > 5: + n_tx = 5 + else: + req = requests.get(blockchain_all+btc+"?limit=50&offset="+str(i)+"&filter=5") + jreq = req.json() + if jreq['txs']: + for transactions in jreq['txs']: + sum = 0 + sum_counter = 0 + for tx in transactions['inputs']: + script_old = tx['script'] + if tx['prev_out']['value'] != 0 and tx['prev_out']['addr'] == btc: + datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time']))) + value = float(tx['prev_out']['value'] / 100000000 ) + u,e = convert(value, transactions['time']) + mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0')) + if script_old != tx['script']: + i += 1 + else: + sum_counter += 1 + sum += value + if sum_counter > 1: + u,e = convert(sum, transactions['time']) + mprint("\t\t\t\t\t----------------------------------------------") + mprint("#" + str(n_tx - i) + "\t\t\t\t Sum:\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR\n".format(sum, u, e).rstrip('0')) + for tx in transactions['out']: + if tx['value'] != 0 and tx['addr'] == btc: + datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time']))) + value = float(tx['value'] / 100000000 ) + u,e = convert(value, transactions['time']) + mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t {0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0')) + #i += 1 + i += 1 + + r = { + 'results': [ + { + 'types': ['text'], + 'values':[ + str(result_text) + ] + } + ] + } + # Debug output on the console + print(result_text) + # Unset the result for the next request + result_text = "" + return r + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 00b1b3214bc301378fa830a6686b13e70709a762 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Wed, 7 Nov 2018 14:28:28 +0100 Subject: [PATCH 249/401] added btc_steroids to the list --- 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 73abd2e..2229316 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', 'btc', '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', 'yara_query'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'btc', 'btc_steroids', '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', 'yara_query'] From 06eba154b5dc7069538074f9a33ce7a2200000fd Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Wed, 7 Nov 2018 14:38:50 +0100 Subject: [PATCH 250/401] added btc_steroids --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5189bb0..da71d5f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ### Expansion modules * [ASN History](misp_modules/modules/expansion/asn_history.py) - a hover and expansion module to expand an AS number with the ASN description and its history. -* [BTC balance](misp_modules/modules/expansion/btc.py) - An expansion hover module to get a blockchain balance from a BTC address in MISP. +* [BTC balance](misp_modules/modules/expansion/btc.py) - An expansion hover module to get a blockchain balance from a BTC address in MISP. Uses the APIs from [https://blockchain.info](blockchain.info) and [https://cryptocompare.com](https://cryptocompare.com) +* [BTC transactions](misp_modules/modules/expansion/btc_steroids.py) - An expansion hover module to get a blockchain balance and the transactions from a BTC address in MISP. * [CIRCL Passive DNS](misp_modules/modules/expansion/circl_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [CIRCL Passive SSL](misp_modules/modules/expansion/circl_passivessl.py) - a hover and expansion module to expand IP addresses with the X.509 certificate seen. * [countrycode](misp_modules/modules/expansion/countrycode.py) - a hover module to tell you what country a URL belongs to. From 91f922b5c477b5c8cecfc70148d5aa99d0c389da Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Wed, 7 Nov 2018 22:53:21 +0900 Subject: [PATCH 251/401] chg: [btc] Removed simple PoC for btc expansion. --- README.md | 1 - misp_modules/modules/expansion/__init__.py | 2 +- misp_modules/modules/expansion/btc.py | 50 ---------------------- 3 files changed, 1 insertion(+), 52 deletions(-) delete mode 100755 misp_modules/modules/expansion/btc.py diff --git a/README.md b/README.md index da71d5f..01ec367 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ### Expansion modules * [ASN History](misp_modules/modules/expansion/asn_history.py) - a hover and expansion module to expand an AS number with the ASN description and its history. -* [BTC balance](misp_modules/modules/expansion/btc.py) - An expansion hover module to get a blockchain balance from a BTC address in MISP. Uses the APIs from [https://blockchain.info](blockchain.info) and [https://cryptocompare.com](https://cryptocompare.com) * [BTC transactions](misp_modules/modules/expansion/btc_steroids.py) - An expansion hover module to get a blockchain balance and the transactions from a BTC address in MISP. * [CIRCL Passive DNS](misp_modules/modules/expansion/circl_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [CIRCL Passive SSL](misp_modules/modules/expansion/circl_passivessl.py) - a hover and expansion module to expand IP addresses with the X.509 certificate seen. diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 2229316..1534fda 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', 'btc', 'btc_steroids', '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', 'yara_query'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'btc_steroids', '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', 'yara_query'] diff --git a/misp_modules/modules/expansion/btc.py b/misp_modules/modules/expansion/btc.py deleted file mode 100755 index da2fbe0..0000000 --- a/misp_modules/modules/expansion/btc.py +++ /dev/null @@ -1,50 +0,0 @@ -import json -import blockchain - -misperrors = {'error': 'Error'} -mispattributes = {'input': ['btc'], 'output': ['text']} -moduleinfo = {'version': '0.1', 'author': 'Steve Clement', - 'description': 'Simple BTC expansion service to \ - get quick information from MISP attributes', - 'module-type': ['expansion', 'hover']} - -moduleconfig = [] - - -def handler(q=False): - if q is False: - return False - request = json.loads(q) - if request.get('btc'): - toquery = request['btc'] - else: - return False - - try: - address = blockchain.blockexplorer.get_address(toquery) - except Exception as e: - misperrors['error'] = e - return misperrors - finalBalance = address.final_balance*(1/100000000) - totalRX = address.total_received*(1/100000000) - totalTX = address.total_sent*(1/100000000) - totalTransactions = address.n_tx - - answer = 'Current balance: \ - {} - \ - {} total received - \ - {} total sent - \ - {} transactions.\ - '.format(finalBalance, totalRX, totalTX, totalTransactions) - r = {'results': [{'types': mispattributes['output'], - 'values':[str(answer)]}]} - return r - - -def introspection(): - return mispattributes - - -def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo From 463d7ae87458a60e3775c0923fee67bec37c51f0 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Wed, 7 Nov 2018 14:57:19 +0100 Subject: [PATCH 252/401] bug fix regarding leftovers between runs --- misp_modules/modules/expansion/btc_steroids.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index b379a63..c4edaa3 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -107,7 +107,8 @@ def handler(q=False): jreq = req.json() except Exception as e: #print(e) - mprint(req.text) + print(req.text) + result_text = "" sys.exit(1) n_tx = jreq['n_tx'] From 5d1583d88b87f6c08b9a9b513d89d777c6fd0f57 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Sun, 11 Nov 2018 15:49:14 +0100 Subject: [PATCH 253/401] chg: [onyphe] fix #252 --- misp_modules/modules/expansion/onyphe.py | 8 ++++---- misp_modules/modules/expansion/onyphe_full.py | 4 ++-- tests/bodyhashdd.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index 86abe7a..c9bca0e 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -65,16 +65,16 @@ def handle_expansion(api, ip, misperrors): for r in result['results']: if r['@category'] == 'pastries': - if r['@type'] == 'pastebin': + if r['source'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % r['key']) elif r['@category'] == 'synscan': asn_list.append(r['asn']) os_target = r['os'] if os_target != 'Unknown': os_list.append(r['os']) - elif r['@category'] == 'resolver' and r['@type'] =='reverse': + elif r['@category'] == 'resolver' and r['type'] =='reverse': domains_resolver.append(r['reverse']) - elif r['@category'] == 'resolver' and r['@type'] =='forward': + elif r['@category'] == 'resolver' and r['type'] =='forward': domains_forward.append(r['forward']) result_filtered['results'].append({'types': ['url'], 'values': urls_pasties, @@ -105,4 +105,4 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig - return moduleinfo \ No newline at end of file + return moduleinfo diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 7a05d12..3d6ef8e 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -315,7 +315,7 @@ def expand_pastries(api, misperror, **kwargs): status_ok = True for item in result['results']: if item['@category'] == 'pastries': - if item['@type'] == 'pastebin': + if item['source'] == 'pastebin': urls_pasties.append('https://pastebin.com/raw/%s' % item['key']) if 'domain' in item: @@ -374,4 +374,4 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig - return moduleinfo \ No newline at end of file + return moduleinfo diff --git a/tests/bodyhashdd.json b/tests/bodyhashdd.json index b6d256c..3bdfa82 100644 --- a/tests/bodyhashdd.json +++ b/tests/bodyhashdd.json @@ -1 +1 @@ -{"module": "hashdd", "md5": "838DE99E82C5B9753BAC96D82C1A8DCB"} +{"module": "hashdd", "md5": "838DE99E82C5B9753BAC96D82C1A8DCC"} From b9f634b506ced81cf4f5d3ff935481084f2ce96a Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 12 Nov 2018 16:14:54 +0100 Subject: [PATCH 254/401] fix: Specifying a yara-python version that works for hash & pe yara modules --- REQUIREMENTS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index cfaf9ad..3bbcc88 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -21,7 +21,7 @@ domaintools_api pygeoip bs4 oauth2 -yara-python +yara-python==3.8.0 sigmatools stix2-patterns maclookup From 58b3a069bfa333cbc8f66efac5936a4461c9eba0 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 12 Nov 2018 16:22:14 +0100 Subject: [PATCH 255/401] fix: Updated yara import error message - Better to 'pip install -I -r REQUIREMENTS' to have the correct yara-python version working for all the modules, than having another one failing with yara hash & pe modules --- misp_modules/modules/expansion/yara_query.py | 2 +- misp_modules/modules/expansion/yara_syntax_validator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/yara_query.py b/misp_modules/modules/expansion/yara_query.py index 565030c..9b24c88 100644 --- a/misp_modules/modules/expansion/yara_query.py +++ b/misp_modules/modules/expansion/yara_query.py @@ -3,7 +3,7 @@ import re try: import yara except (OSError, ImportError): - print("yara is missing, use 'pip3 install yara' to install it.") + print("yara is missing, use 'pip3 install -I -r REQUIREMENTS' from the root of this repository to install it.") misperrors = {'error': 'Error'} moduleinfo = {'version': '1', 'author': 'Christian STUDER', diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index c68d934..804ebd9 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -3,7 +3,7 @@ import requests try: import yara except (OSError, ImportError): - print("yara is missing, use 'pip3 install yara' to install it.") + print("yara is missing, use 'pip3 install -I -r REQUIREMENTS' from the root of this repository to install it.") misperrors = {'error': 'Error'} mispattributes = {'input': ['yara'], 'output': ['text']} From 8285ff324fcd03c97ee6a077b05cd5a4430e9712 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Tue, 13 Nov 2018 15:30:06 +0100 Subject: [PATCH 256/401] API changes reflected --- misp_modules/modules/expansion/btc_steroids.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index c4edaa3..b6cb2cc 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -24,10 +24,15 @@ start_time = 0 conversion_rates = {} def get_consumption(output=False): - req = requests.get(converter_rls) - jreq = req.json() - minute = str(jreq['Minute']['CallsLeft']['Histo']) - hour = str(jreq['Hour']['CallsLeft']['Histo']) + try: + req = requests.get(converter_rls) + jreq = req.json() + minute = str(jreq['Data']['calls_left']['minute']) + hour = str(jreq['Data']['calls_left']['hour']) + except Exception as e: + print(e) + minute = str(-1) + hour = str(-1) # Debug out for the console print("Calls left this minute / hour: " + minute + " / " + hour) return minute, hour From 3e254289784a6a6f978ec43d0e1323c25d3fa160 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Tue, 13 Nov 2018 15:34:33 +0100 Subject: [PATCH 257/401] debug removed --- misp_modules/modules/expansion/btc_steroids.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index b6cb2cc..adce9b6 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -29,8 +29,7 @@ def get_consumption(output=False): jreq = req.json() minute = str(jreq['Data']['calls_left']['minute']) hour = str(jreq['Data']['calls_left']['hour']) - except Exception as e: - print(e) + except: minute = str(-1) hour = str(-1) # Debug out for the console From 299e97d1cec6d2c536d05dc41a3a05d2b1cebede Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 13 Nov 2018 15:40:47 +0100 Subject: [PATCH 258/401] add: Added imphash to input attribute types --- misp_modules/modules/expansion/yara_query.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/yara_query.py b/misp_modules/modules/expansion/yara_query.py index 9b24c88..3b85bcb 100644 --- a/misp_modules/modules/expansion/yara_query.py +++ b/misp_modules/modules/expansion/yara_query.py @@ -11,11 +11,12 @@ moduleinfo = {'version': '1', 'author': 'Christian STUDER', 'module-type': ['expansion', 'hover'], 'require_standard_format': True} moduleconfig = [] -mispattributes = {'input': ['md5', 'sha1', 'sha256', 'filename|md5', 'filename|sha1', 'filename|sha256'], 'output': ['yara']} +mispattributes = {'input': ['md5', 'sha1', 'sha256', 'filename|md5', 'filename|sha1', 'filename|sha256', 'imphash'], 'output': ['yara']} def get_hash_condition(hashtype, hashvalue): - condition = 'hash.{}(0, filesize) == "{}"'.format(hashtype, hashvalue.lower()) - return condition, 'hash' + hashvalue = hashvalue.lower() + required_module, params = ('pe', '()') if hashtype == 'imphash' else ('hash', '(0, filesize)') + return '{}.{}{} == "{}"'.format(required_module, hashtype, params, hashvalue), required_module def handler(q=False): if q is False: @@ -31,7 +32,7 @@ def handler(q=False): _, value = value.split('|') condition, required_module = get_hash_condition(attribute_type, value) import_section = 'import "{}"'.format(required_module) - rule_start = 'import "hash" \r\nrule %s_%s {' % (attribute_type.upper(), re.sub(r'\W+', '_', uuid)) if uuid else 'import "hash"\r\nrule %s {' % attribute_type.upper() + rule_start = '%s\r\nrule %s_%s {' % (import_section, attribute_type.upper(), re.sub(r'\W+', '_', uuid)) if uuid else '%s\r\nrule %s {' % (import_section, attribute_type.upper()) condition = '\tcondition:\r\n\t\t{}'.format(condition) rule = '\r\n'.join([rule_start, condition, '}']) try: From 4149a07eff25d44733b5bc73b9fa918aa573f5c8 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 13 Nov 2018 16:00:55 +0100 Subject: [PATCH 259/401] add: Added test files for yara to test yara library & potentially yara syntax --- tests/yara_hash_module_test.yara | 7 +++++++ tests/yara_pe_module_test.yara | 5 +++++ tests/yara_test.py | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 tests/yara_hash_module_test.yara create mode 100644 tests/yara_pe_module_test.yara create mode 100644 tests/yara_test.py diff --git a/tests/yara_hash_module_test.yara b/tests/yara_hash_module_test.yara new file mode 100644 index 0000000..4674eef --- /dev/null +++ b/tests/yara_hash_module_test.yara @@ -0,0 +1,7 @@ +import "hash" +rule oui { + condition: + hash.md5(0, filesize) == "8764605c6f388c89096b534d33565802" and + hash.sha1(0, filesize) == "46aba99aa7158e4609aaa72b50990842fd22ae86" and + hash.sha256(0, filesize) == "ec5aedf5ecc6bdadd4120932170d1b10f6cfa175cfda22951dfd882928ab279b" +} diff --git a/tests/yara_pe_module_test.yara b/tests/yara_pe_module_test.yara new file mode 100644 index 0000000..4998b84 --- /dev/null +++ b/tests/yara_pe_module_test.yara @@ -0,0 +1,5 @@ +import "pe" +rule my_pe { + condition: + pe.imphash() == "eecc824da5b175f530705611127a6b41" +} diff --git a/tests/yara_test.py b/tests/yara_test.py new file mode 100644 index 0000000..ea88f03 --- /dev/null +++ b/tests/yara_test.py @@ -0,0 +1,22 @@ +import sys +try: + import yara +except (OSError, ImportError): + sys.exit("yara is missing, use 'pip3 install -I -r REQUIREMENTS' from the root of this repository to install it.") + +# Usage: python3 yara_test.py [yara files] +# with any yara file(s) in order to test if yara library is correctly installed. +# (it is also validating yara syntax) +# +# If no argument is given, this script takes the 2 yara test rules in the same directory +# in order to test if both yara modules we need work properly. + +files = sys.argv[1:] if len(sys.argv) > 1 else ['yara_hash_module_test.yara', 'yara_pe_module_test.yara'] + +for file_ in files: + try: + yara.compile(file_) + status = "Valid syntax" + except Exception as e: + status = e + print("{}: {}".format(file_, status)) From 37476058b3aaf2f5f542aa039e832c8bc7c04913 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 13 Nov 2018 16:34:13 +0100 Subject: [PATCH 260/401] add: Added yara_query module documentation, update yara_syntax_validator documentation & generated updated documentation markdown --- doc/documentation.md | 29 ++++++++++++++++++++++++ doc/expansion/yara_query.json | 9 ++++++++ doc/expansion/yara_syntax_validator.json | 7 +++++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 doc/expansion/yara_query.json diff --git a/doc/documentation.md b/doc/documentation.md index a11bcfb..31b6015 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -238,11 +238,40 @@ An expansion module for IBM X-Force Exchange. ----- +#### [yara_query](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/yara_query.py) + + + +An expansion & hover module to translate any hash attribute into a yara rule. +- **requirements**: +>yara-python python library +- **features**: +>The module takes a hash attribute (md5, sha1, sha256, imphash) as input, and is returning a YARA rule from it. This YARA rule is also validated using the same method as in 'yara_syntax_validator' module. +>Both hover and expansion functionalities are supported with this module, where the hover part is displaying the resulting YARA rule and the expansion part allows you to add the rule as a new attribute, as usual with expansion modules. +- **input**: +>MISP Hash attribute (md5, sha1, sha256, imphash, or any of the composite attribute with filename and one of the previous hash type). +- **output**: +>YARA rule. +- **references**: +>https://virustotal.github.io/yara/, https://github.com/virustotal/yara-python + +----- + #### [yara_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/yara_syntax_validator.py) An expansion hover module to perform a syntax check on if yara rules are valid or not. +- **requirements**: +>yara_python python library +- **input**: +>YARA rule attribute. +- **output**: +>Text to inform users if their rule is valid. +- **references**: +>http://virustotal.github.io/yara/ +- **features**: +>This modules simply takes a YARA rule as input, and checks its syntax. It returns then a confirmation if the syntax is valid, otherwise the syntax error is displayed. ----- diff --git a/doc/expansion/yara_query.json b/doc/expansion/yara_query.json new file mode 100644 index 0000000..408353d --- /dev/null +++ b/doc/expansion/yara_query.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion & hover module to translate any hash attribute into a yara rule.", + "logo": "logos/yara.png", + "requirements": ["yara-python python library"], + "features": "The module takes a hash attribute (md5, sha1, sha256, imphash) as input, and is returning a YARA rule from it. This YARA rule is also validated using the same method as in 'yara_syntax_validator' module.\nBoth hover and expansion functionalities are supported with this module, where the hover part is displaying the resulting YARA rule and the expansion part allows you to add the rule as a new attribute, as usual with expansion modules.", + "input": "MISP Hash attribute (md5, sha1, sha256, imphash, or any of the composite attribute with filename and one of the previous hash type).", + "output": "YARA rule.", + "references": ["https://virustotal.github.io/yara/", "https://github.com/virustotal/yara-python"] +} diff --git a/doc/expansion/yara_syntax_validator.json b/doc/expansion/yara_syntax_validator.json index 891aa5a..93a96ee 100644 --- a/doc/expansion/yara_syntax_validator.json +++ b/doc/expansion/yara_syntax_validator.json @@ -1,4 +1,9 @@ { "description": "An expansion hover module to perform a syntax check on if yara rules are valid or not.", - "logo": "logos/yara.png" + "logo": "logos/yara.png", + "requirements": ["yara_python python library"], + "input": "YARA rule attribute.", + "output": "Text to inform users if their rule is valid.", + "references": ["http://virustotal.github.io/yara/"], + "features": "This modules simply takes a YARA rule as input, and checks its syntax. It returns then a confirmation if the syntax is valid, otherwise the syntax error is displayed." } From 2d47b670f8409d15a6fed4a23a46a6cc13742619 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 13 Nov 2018 16:50:49 +0100 Subject: [PATCH 261/401] fix: Displaying documentation items of each module by alphabetic order - Also regenerated updated documentation markdown --- doc/documentation.md | 88 +++++++++++++++++------------------ doc/generate_documentation.py | 2 +- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/doc/documentation.md b/doc/documentation.md index 31b6015..ecb62fa 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -243,8 +243,6 @@ An expansion module for IBM X-Force Exchange. An expansion & hover module to translate any hash attribute into a yara rule. -- **requirements**: ->yara-python python library - **features**: >The module takes a hash attribute (md5, sha1, sha256, imphash) as input, and is returning a YARA rule from it. This YARA rule is also validated using the same method as in 'yara_syntax_validator' module. >Both hover and expansion functionalities are supported with this module, where the hover part is displaying the resulting YARA rule and the expansion part allows you to add the rule as a new attribute, as usual with expansion modules. @@ -254,6 +252,8 @@ An expansion & hover module to translate any hash attribute into a yara rule. >YARA rule. - **references**: >https://virustotal.github.io/yara/, https://github.com/virustotal/yara-python +- **requirements**: +>yara-python python library ----- @@ -262,16 +262,16 @@ An expansion & hover module to translate any hash attribute into a yara rule. An expansion hover module to perform a syntax check on if yara rules are valid or not. -- **requirements**: ->yara_python python library +- **features**: +>This modules simply takes a YARA rule as input, and checks its syntax. It returns then a confirmation if the syntax is valid, otherwise the syntax error is displayed. - **input**: >YARA rule attribute. - **output**: >Text to inform users if their rule is valid. - **references**: >http://virustotal.github.io/yara/ -- **features**: ->This modules simply takes a YARA rule as input, and checks its syntax. It returns then a confirmation if the syntax is valid, otherwise the syntax error is displayed. +- **requirements**: +>yara_python python library ----- @@ -283,12 +283,12 @@ 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 +- **references**: +>https://community.softwaregrp.com/t5/ArcSight-Connectors/ArcSight-Common-Event-Format-CEF-Guide/ta-p/1589306?attachment-id=65537 ----- @@ -297,8 +297,6 @@ Module to export a MISP event in CEF format. 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. > @@ -318,12 +316,14 @@ This module is used to export MISP events containing transaction objects into Go > - '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). +- **references**: +>http://goaml.unodc.org/ +- **requirements**: +>PyMISP, MISP objects ----- @@ -356,16 +356,16 @@ OSQuery export of a MISP event. #### [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**: ->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. +- **references**: +>https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html +- **requirements**: +>PyMISP, asciidoctor ----- @@ -380,16 +380,16 @@ Skeleton export module. 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 +- **references**: +>https://www.anomali.com/platform/threatstream, https://github.com/threatstream +- **requirements**: +>csv ----- @@ -398,17 +398,17 @@ Module to export a structured CSV file for uploading to threatStream. 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 +- **references**: +>https://www.threatconnect.com +- **requirements**: +>csv ----- @@ -417,8 +417,6 @@ Module to export a structured CSV file for uploading to ThreatConnect. #### [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**: ->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, '). @@ -426,12 +424,14 @@ Module to import MISP attributes from a csv file. > >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 +- **references**: +>https://tools.ietf.org/html/rfc4180, https://tools.ietf.org/html/rfc7111 +- **requirements**: +>PyMISP ----- @@ -442,12 +442,12 @@ Module to import MISP attributes from a csv file. 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 +- **references**: +>https://cuckoosandbox.org/, https://github.com/cuckoosandbox/cuckoo ----- @@ -469,16 +469,16 @@ Module to import emails in MISP. 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. +- **references**: +>http://goaml.unodc.org/ +- **requirements**: +>PyMISP ----- @@ -509,16 +509,16 @@ Optical Character Recognition (OCR) module for MISP. #### [openiocimport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/openiocimport.py) 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 +- **references**: +>https://www.fireeye.com/blog/threat-research/2013/10/openioc-basics.html +- **requirements**: +>PyMISP ----- @@ -528,12 +528,12 @@ 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 +- **references**: +>https://www.threattrack.com/malware-analysis.aspx ----- @@ -542,16 +542,16 @@ Module to import ThreatAnalyzer archive.zip / analysis.json files. 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 +- **references**: +>https://www.vmray.com/ +- **requirements**: +>vmray_rest_api ----- diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py index 5a59814..6be61de 100644 --- a/doc/generate_documentation.py +++ b/doc/generate_documentation.py @@ -24,7 +24,7 @@ def generate_doc(root_path): 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(): + for field, value in sorted(definition.items()): if value: value = ', '.join(value) if isinstance(value, list) else '{}'.format(value.replace('\n', '\n>')) markdown.append('- **{}**:\n>{}\n'.format(field, value)) From 8817de476572a10a9c9d03258ec81ca70f3d926d Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Fri, 16 Nov 2018 13:27:54 +0100 Subject: [PATCH 262/401] fix: threatanalyzer_import - bugfix for TA6.1 behavior --- misp_modules/modules/import_mod/threatanalyzer_import.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index 2e3a507..c5d8ba3 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.9', 'author': 'Christophe Vandeplas', +moduleinfo = {'version': '0.10', 'author': 'Christophe Vandeplas', 'description': 'Import for ThreatAnalyzer archive.zip/analysis.json files', 'module-type': ['import']} @@ -118,8 +118,15 @@ def process_analysis_json(analysis_json): # 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']: + # 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 + for subsection in ['http_command', 'http_header']: + if isinstance(connection_section_connection[subsection], dict): + connection_section_connection[subsection] = [connection_section_connection[subsection]] + 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'] From 07a4faf67aea12cdbd5739b011a2c14b19d210cc Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 15 Nov 2018 19:43:14 +0100 Subject: [PATCH 263/401] add: Started filling some expansion modules documentation --- doc/expansion/asn_history.json | 6 +++++- doc/expansion/btc.json | 5 ++++- doc/expansion/circl_passivedns.json | 7 ++++++- doc/expansion/circl_passivessl.json | 7 ++++++- doc/expansion/countrycode.json | 5 ++++- doc/expansion/crowdstrike_falcon.json | 7 ++++++- doc/expansion/cve.json | 7 ++++++- doc/logos/bitcoin.png | Bin 0 -> 9918 bytes doc/logos/cve.png | Bin 0 -> 20015 bytes 9 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 doc/logos/bitcoin.png create mode 100644 doc/logos/cve.png diff --git a/doc/expansion/asn_history.json b/doc/expansion/asn_history.json index 936feba..b3eea26 100644 --- a/doc/expansion/asn_history.json +++ b/doc/expansion/asn_history.json @@ -1,4 +1,8 @@ { "description": "Query an ASN description history service (https://github.com/CIRCL/ASN-Description-History.git).", - "requirements": ["asnhistory"] + "requirements": ["asnhistory python library"], + "features": "The module takes an AS number attribute as input and displays its description and history.\n\nFor a proper working, a communication with a redis database is needed, thus 3 parameters are needed:\n- host, the address of the redis server\n- port, the port used by redis\n- db, the index of the database used\n", + "references": ["https://github.com/CIRCL/ASN-Description-History.git"], + "input": "Autonomous system number.", + "output": "Text containing a description of the ASN and its history." } diff --git a/doc/expansion/btc.json b/doc/expansion/btc.json index 3aeceab..fd264d8 100644 --- a/doc/expansion/btc.json +++ b/doc/expansion/btc.json @@ -1,3 +1,6 @@ { - "description": "An expansion hover module to get a blockchain balance from a BTC address in MISP." + "description": "An expansion hover module to get a blockchain balance from a BTC address in MISP.", + "logo": "logos/bitcoin.png", + "input": "btc address attribute.", + "output": "Text to describe the blockchain balance and the transactions related to the btc address in input." } diff --git a/doc/expansion/circl_passivedns.json b/doc/expansion/circl_passivedns.json index 664ca77..fda50eb 100644 --- a/doc/expansion/circl_passivedns.json +++ b/doc/expansion/circl_passivedns.json @@ -1,4 +1,9 @@ { "description": "Module to access CIRCL Passive DNS.", - "logo": "logos/passivedns.png" + "logo": "logos/passivedns.png", + "requirements": ["pypdns: Passive DNS python library", "A CIRCL passive DNS account with username & password"], + "input": "Hostname, domain, or ip-address attribute.", + "ouput": "Text describing passive DNS information related to the input attribute.", + "features": "This module takes a hostname, domain or ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive DNS REST API to get and display information about this input.\n\nTo make it work a username and a password are thus required to authenticate to the CIRCL Passive DNS API.", + "references": ["https://www.circl.lu/services/passive-dns/", "https://datatracker.ietf.org/doc/draft-dulaunoy-dnsop-passive-dns-cof/"] } diff --git a/doc/expansion/circl_passivessl.json b/doc/expansion/circl_passivessl.json index 2015b59..ec449ee 100644 --- a/doc/expansion/circl_passivessl.json +++ b/doc/expansion/circl_passivessl.json @@ -1,4 +1,9 @@ { "description": "Modules to access CIRCL Passive SSL.", - "logo": "logos/passivessl.png" + "logo": "logos/passivessl.png", + "requirements": ["pypssl: Passive SSL python library", "A CIRCL passive SSL account with username & password"], + "input": "Ip-address attribute.", + "output": "Text describing passive SSL information related to the input attribute.", + "features": "This module takes an ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive SSL REST API to get and display information about this input.\n\nTo make it work a username and a password are thus required to authenticate to the CIRCL Passive SSL API.", + "references": ["https://www.circl.lu/services/passive-ssl/"] } diff --git a/doc/expansion/countrycode.json b/doc/expansion/countrycode.json index 367c14b..c6214e5 100644 --- a/doc/expansion/countrycode.json +++ b/doc/expansion/countrycode.json @@ -1,3 +1,6 @@ { - "description": "Module to expand country codes." + "description": "Module to expand country codes.", + "input": "Hostname or domain attribute.", + "output": "Text with the country code the input belongs to.", + "features": "The module takes a domain or a hostname as input, and returns the country it belongs to.\n\nFor non country domains, a list of the most common possible extensions is used." } diff --git a/doc/expansion/crowdstrike_falcon.json b/doc/expansion/crowdstrike_falcon.json index 0faa6c0..4392561 100644 --- a/doc/expansion/crowdstrike_falcon.json +++ b/doc/expansion/crowdstrike_falcon.json @@ -1,4 +1,9 @@ { "description": "Module to query Crowdstrike Falcon.", - "logo": "logos/crowdstrike.png" + "logo": "logos/crowdstrike.png", + "requirements": ["A CrowdStrike API access (API id & key)"], + "input": "A MISP attribute included in the following list:\n- domain\n- email-attachment\n- email-dst\n- email-reply-to\n- email-src\n- email-subject\n- filename\n- hostname\n- ip-src\n- ip-dst\n- md5\n- mutex\n- regkey\n- sha1\n- sha256\n- uri\n- url\n- user-agent\n- whois-registrant-email\n- x509-fingerprint-md5", + "output": "MISP attributes fetched after the CrowdStrike API has been queried, included in the following list:\n- hostname\n- email-src\n- email-subject\n- filename\n- md5\n- sha1\n- sha256\n- ip-dst\n- ip-dst\n- mutex\n- regkey\n- url\n- user-agent\n- x509-fingerprint-md5", + "references": ["https://www.crowdstrike.com/products/crowdstrike-falcon-faq/"], + "features": "This module takes a MISP attribute as input to query a CrowdStrike Falcon API, using an api_id and an apikey.\n\nThe API returns then the result of the query with some types we map into compatible types we add as MISP attributes." } diff --git a/doc/expansion/cve.json b/doc/expansion/cve.json index afc4c33..04f131f 100644 --- a/doc/expansion/cve.json +++ b/doc/expansion/cve.json @@ -1,3 +1,8 @@ { - "description": "An expansion hover module to expand information about CVE id." + "description": "An expansion hover module to expand information about CVE id.", + "logo": "logos/cve.png", + "input": "Vulnerability attribute.", + "output": "Text giving information about the CVE related to the Vulnerability.", + "references": ["https://cve.circl.lu/", "https://cve.mitre.org/"], + "features": "The module takes a vulnerability attribute as input and queries the CIRCL CVE search API to get information about the vulnerability as it is described in the list of CVEs." } diff --git a/doc/logos/bitcoin.png b/doc/logos/bitcoin.png new file mode 100644 index 0000000000000000000000000000000000000000..e80ad6d11c87de6ee8fdb039e7d47b3157a0e17a GIT binary patch literal 9918 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 literal 0 HcmV?d00001 diff --git a/doc/logos/cve.png b/doc/logos/cve.png new file mode 100644 index 0000000000000000000000000000000000000000..315ccd8190b154c555725f3981c6695035a21dd9 GIT binary patch literal 20015 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 L%^8xZ&*gsu`sR15 literal 0 HcmV?d00001 From 1b44668094c08a4aa226d259dc6623dcd0984575 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 15 Nov 2018 19:44:43 +0100 Subject: [PATCH 264/401] fix: Added hostname attribute support as it is intended --- misp_modules/modules/expansion/countrycode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/countrycode.py b/misp_modules/modules/expansion/countrycode.py index 9f22c40..fb38fe4 100755 --- a/misp_modules/modules/expansion/countrycode.py +++ b/misp_modules/modules/expansion/countrycode.py @@ -29,12 +29,12 @@ def handler(q=False): if q is False: return False request = json.loads(q) - domain = request["domain"] + domain = request["domain"] if "domain" in request else request["hostname"] # Get the extension ext = domain.split(".")[-1] - # Check if if's a common, non country one + # Check if it's a common, non country one if ext in common_tlds.keys(): val = common_tlds[ext] else: From b778dd5e67933ce5127e3d4d02cf45be51dc7489 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Thu, 15 Nov 2018 19:47:02 +0100 Subject: [PATCH 265/401] fix: Fixed type of the result in case of exception - Set as str since some exception types are not jsonable --- misp_modules/modules/expansion/dbl_spamhaus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/dbl_spamhaus.py b/misp_modules/modules/expansion/dbl_spamhaus.py index 306ea21..f372961 100644 --- a/misp_modules/modules/expansion/dbl_spamhaus.py +++ b/misp_modules/modules/expansion/dbl_spamhaus.py @@ -49,7 +49,7 @@ def handler(q=False): query_result = resolver.query(query, 'A')[0] result = "{} - {}".format(requested_value, dbl_mapping[str(query_result)]) except Exception as e: - result = e + result = str(e) return {'results': [{'types': mispattributes.get('output'), 'values': result}]} def introspection(): From 87e92383650ad1f551982932d1e028fd25d921f0 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 19 Nov 2018 10:29:25 +0100 Subject: [PATCH 266/401] add: More documentation on expansion modules --- doc/expansion/crowdstrike_falcon.json | 4 ++-- doc/expansion/dbl_spamhaus.json | 7 ++++++- doc/expansion/dns.json | 6 +++++- doc/expansion/domaintools.json | 7 ++++++- doc/expansion/eupi.json | 7 ++++++- doc/expansion/farsight_passivedns.json | 7 ++++++- doc/expansion/geoip_country.json | 8 +++++++- doc/logos/maxmind.png | Bin 0 -> 25782 bytes 8 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 doc/logos/maxmind.png diff --git a/doc/expansion/crowdstrike_falcon.json b/doc/expansion/crowdstrike_falcon.json index 4392561..07e9dbd 100644 --- a/doc/expansion/crowdstrike_falcon.json +++ b/doc/expansion/crowdstrike_falcon.json @@ -3,7 +3,7 @@ "logo": "logos/crowdstrike.png", "requirements": ["A CrowdStrike API access (API id & key)"], "input": "A MISP attribute included in the following list:\n- domain\n- email-attachment\n- email-dst\n- email-reply-to\n- email-src\n- email-subject\n- filename\n- hostname\n- ip-src\n- ip-dst\n- md5\n- mutex\n- regkey\n- sha1\n- sha256\n- uri\n- url\n- user-agent\n- whois-registrant-email\n- x509-fingerprint-md5", - "output": "MISP attributes fetched after the CrowdStrike API has been queried, included in the following list:\n- hostname\n- email-src\n- email-subject\n- filename\n- md5\n- sha1\n- sha256\n- ip-dst\n- ip-dst\n- mutex\n- regkey\n- url\n- user-agent\n- x509-fingerprint-md5", + "output": "MISP attributes mapped after the CrowdStrike API has been queried, included in the following list:\n- hostname\n- email-src\n- email-subject\n- filename\n- md5\n- sha1\n- sha256\n- ip-dst\n- ip-dst\n- mutex\n- regkey\n- url\n- user-agent\n- x509-fingerprint-md5", "references": ["https://www.crowdstrike.com/products/crowdstrike-falcon-faq/"], - "features": "This module takes a MISP attribute as input to query a CrowdStrike Falcon API, using an api_id and an apikey.\n\nThe API returns then the result of the query with some types we map into compatible types we add as MISP attributes." + "features": "This module takes a MISP attribute as input to query a CrowdStrike Falcon API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes.\n\nPlease note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported." } diff --git a/doc/expansion/dbl_spamhaus.json b/doc/expansion/dbl_spamhaus.json index b691007..ea73dcb 100644 --- a/doc/expansion/dbl_spamhaus.json +++ b/doc/expansion/dbl_spamhaus.json @@ -1,4 +1,9 @@ { "description": "Module to check Spamhaus DBL for a domain name.", - "logo": "logos/spamhaus.jpg" + "logo": "logos/spamhaus.jpg", + "requirements": ["dnspython3: DNS python3 library"], + "input": "Domain or hostname attribute.", + "output": "Information about the nature of the input.", + "references": ["https://www.spamhaus.org/faq/section/Spamhaus%20DBL"], + "features": "This modules takes a domain or a hostname in input and queries the Domain Block List provided by Spamhaus to determine what kind of domain it is.\n\nDBL then returns a response code corresponding to a certain classification of the domain we display. If the queried domain is not in the list, it is also mentionned.\n\nPlease note that composite MISP attributes containing domain or hostname are supported as well." } diff --git a/doc/expansion/dns.json b/doc/expansion/dns.json index 2ca7e42..dc43b64 100644 --- a/doc/expansion/dns.json +++ b/doc/expansion/dns.json @@ -1,3 +1,7 @@ { - "description": "A simple DNS expansion service to resolve IP address from MISP attributes." + "description": "A simple DNS expansion service to resolve IP address from domain MISP attributes.", + "requirements": ["dnspython3: DNS python3 library"], + "input": "Domain or hostname attribute.", + "output": "IP address resolving the input.", + "features": "The module takes a domain of hostname attribute as input, and tries to resolve it. If no error is encountered, the IP address that resolves the domain is returned, otherwise the origin of the error is displayed.\n\nThe address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8).\n\nPlease note that composite MISP attributes containing domain or hostname are supported as well." } diff --git a/doc/expansion/domaintools.json b/doc/expansion/domaintools.json index 5ed0cb2..849028c 100644 --- a/doc/expansion/domaintools.json +++ b/doc/expansion/domaintools.json @@ -1,4 +1,9 @@ { "description": "DomainTools MISP expansion module.", - "logo": "logos/domaintools.png" + "logo": "logos/domaintools.png", + "requirements": ["Domaintools python library", "A Domaintools API access (username & apikey)"], + "input": "A MISP attribute included in the following list:\n- domain\n- hostname\n- email-src\n- email-dst\n- target-email\n- whois-registrant-email\n- whois-registrant-name\n- whois-registrant-phone\n- ip-src\n- ip-dst", + "output": "MISP attributes mapped after the Domaintools API has been queried, included in the following list:\n- whois-registrant-email\n- whois-registrant-phone\n- whois-registrant-name\n- whois-registrar\n- whois-creation-date\n- text\n- domain", + "references": ["https://www.domaintools.com/"], + "features": "This module takes a MISP attribute as input to query the Domaintools API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes.\n\nPlease note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported." } diff --git a/doc/expansion/eupi.json b/doc/expansion/eupi.json index 42da8aa..02a16fb 100644 --- a/doc/expansion/eupi.json +++ b/doc/expansion/eupi.json @@ -1,4 +1,9 @@ { "description": "A module to query the Phishing Initiative service (https://phishing-initiative.lu).", - "logo": "logos/eupi.png" + "logo": "logos/eupi.png", + "requirements": ["pyeupi: eupi python library", "An access to the Phishing Initiative API (apikey & url)"], + "input": "A domain, hostname or url MISP attribute.", + "output": "Text containing information about the input, resulting from the query on Phishing Initiative.", + "references": ["https://phishing-initiative.eu/?lang=en"], + "features": "This module takes a domain, hostname or url MISP attribute as input to query the Phishing Initiative API. The API returns then the result of the query with some information about the value queried.\n\nPlease note that composite attributes containing domain or hostname are also supported." } diff --git a/doc/expansion/farsight_passivedns.json b/doc/expansion/farsight_passivedns.json index 6fd038b..2c1bf05 100644 --- a/doc/expansion/farsight_passivedns.json +++ b/doc/expansion/farsight_passivedns.json @@ -1,4 +1,9 @@ { "description": "Module to access Farsight DNSDB Passive DNS.", - "logo": "logos/farsight.png" + "logo": "logos/farsight.png", + "requirements": ["An access to the Farsight Passive DNS API (apikey)"], + "input": "A domain, hostname or IP address MISP attribute.", + "output": "Text containing information about the input, resulting from the query on the Farsight Passive DNS API.", + "references": ["https://www.farsightsecurity.com/"], + "features": "This module takes a domain, hostname or IP address MISP attribute as input to query the Farsight Passive DNS API. The API returns then the result of the query with some information about the value queried." } diff --git a/doc/expansion/geoip_country.json b/doc/expansion/geoip_country.json index fb3bf33..9db49a2 100644 --- a/doc/expansion/geoip_country.json +++ b/doc/expansion/geoip_country.json @@ -1,3 +1,9 @@ { - "description": "Module to query a local copy of Maxminds Geolite database." + "description": "Module to query a local copy of Maxmind's Geolite database.", + "logo": "logos/maxmind.png", + "requirements": ["A local copy of Maxmind's Geolite database"], + "input": "An IP address MISP Attribute.", + "output": "Text containing information about the location of the IP address.", + "references": ["https://www.maxmind.com/en/home"], + "features": "This module takes an IP address MISP attribute as input and queries a local copy of the Maxmind's Geolite database to get information about the location of this IP address.\n\nPlease note that composite attributes domain|ip are also supported." } diff --git a/doc/logos/maxmind.png b/doc/logos/maxmind.png new file mode 100644 index 0000000000000000000000000000000000000000..8f8a6c6eeb86e4da5c024539b7ab4c6d492bf398 GIT binary patch literal 25782 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

Cz4h}SCsQ>(uw*xfx`$x@&WLHZ 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 M_BE~Y%XXpv15Y~0(*OVf literal 0 HcmV?d00001 From 22173c249ea0ee8eeceb9238f8f7418b7c3b29d8 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 19 Nov 2018 11:32:36 +0100 Subject: [PATCH 267/401] add: Update to support sha1 & sha256 attributes --- misp_modules/modules/expansion/hashdd.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/hashdd.py b/misp_modules/modules/expansion/hashdd.py index beeaf8e..2ab346b 100755 --- a/misp_modules/modules/expansion/hashdd.py +++ b/misp_modules/modules/expansion/hashdd.py @@ -2,8 +2,8 @@ import json import requests misperrors = {'error': 'Error'} -mispattributes = {'input': ['md5'], 'output': ['text']} -moduleinfo = {'version': '0.1', 'author': 'Alexandre Dulaunoy', 'description': 'An expansion module to check hashes against hashdd.com including NSLR dataset.', 'module-type': ['hover']} +mispattributes = {'input': ['md5', 'sha1', 'sha256'], 'output': ['text']} +moduleinfo = {'version': '0.2', 'author': 'Alexandre Dulaunoy', 'description': 'An expansion module to check hashes against hashdd.com including NSLR dataset.', 'module-type': ['hover']} moduleconfig = [] hashddapi_url = 'https://api.hashdd.com/' @@ -11,11 +11,15 @@ hashddapi_url = 'https://api.hashdd.com/' def handler(q=False): if q is False: return False + v = None request = json.loads(q) - if not request.get('md5'): - misperrors['error'] = 'MD5 hash value is missing missing' + for input_type in mispattributes['input']: + if request.get(input_type): + v = request[input_type].upper() + break + if v is None: + misperrors['error'] = 'Hash value is missing.' return misperrors - v = request.get('md5').upper() r = requests.post(hashddapi_url, data={'hash':v}) if r.status_code == 200: state = json.loads(r.text) From 42fabfee63869ebf6c60d4801e8e439617efab8d Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 19 Nov 2018 11:34:21 +0100 Subject: [PATCH 268/401] add: Added new documentation for hashdd module --- doc/expansion/hashdd.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 doc/expansion/hashdd.json diff --git a/doc/expansion/hashdd.json b/doc/expansion/hashdd.json new file mode 100644 index 0000000..5f9c837 --- /dev/null +++ b/doc/expansion/hashdd.json @@ -0,0 +1,7 @@ +{ + "description": "A hover module to check hashes against hashdd.com including NSLR dataset.", + "input": "A hash MISP attribute (md5)", + "output": "Text describing the known level of the hash in the hashdd databases.", + "references": ["https://hashdd.com/"], + "features": "This module takes a hash attribute as input to check its known level, using the hashdd API. This information is then displayed." +} From be3063f3c61e82278fd6d853842c4b6af74d84c1 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 19 Nov 2018 17:04:18 +0100 Subject: [PATCH 269/401] fix: Typo on input type --- doc/logos/intelmq.png | Bin 0 -> 30358 bytes misp_modules/modules/expansion/onyphe.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 doc/logos/intelmq.png diff --git a/doc/logos/intelmq.png b/doc/logos/intelmq.png new file mode 100644 index 0000000000000000000000000000000000000000..fad627cc4627f70195e7bf798beb839857322e23 GIT binary patch literal 30358 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 literal 0 HcmV?d00001 diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index c9bca0e..e9c496a 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -9,7 +9,7 @@ except ImportError: misperrors = {'error': 'Error'} -mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domains'], 'output': ['hostname', 'domain', 'ip-src', 'ip-dst','url']} +mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domain'], 'output': ['hostname', 'domain', 'ip-src', 'ip-dst','url']} # possible module-types: 'expansion', 'hover' or both moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', 'description': 'Query on Onyphe', From b2fcc3374db474f0a6f387eb542718d3df3909c9 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 19 Nov 2018 17:05:55 +0100 Subject: [PATCH 270/401] add: Updated more expansion documentation files --- doc/expansion/hashdd.json | 2 +- doc/expansion/intelmq_eventdb.json | 8 +++++++- doc/expansion/ipasn.json | 7 ++++++- doc/expansion/iprep.json | 7 ++++++- doc/expansion/onyphe.json | 7 ++++++- doc/expansion/onyphe_full.json | 7 ++++++- 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/doc/expansion/hashdd.json b/doc/expansion/hashdd.json index 5f9c837..d963820 100644 --- a/doc/expansion/hashdd.json +++ b/doc/expansion/hashdd.json @@ -1,6 +1,6 @@ { "description": "A hover module to check hashes against hashdd.com including NSLR dataset.", - "input": "A hash MISP attribute (md5)", + "input": "A hash MISP attribute (md5).", "output": "Text describing the known level of the hash in the hashdd databases.", "references": ["https://hashdd.com/"], "features": "This module takes a hash attribute as input to check its known level, using the hashdd API. This information is then displayed." diff --git a/doc/expansion/intelmq_eventdb.json b/doc/expansion/intelmq_eventdb.json index 7746551..bc48414 100644 --- a/doc/expansion/intelmq_eventdb.json +++ b/doc/expansion/intelmq_eventdb.json @@ -1,3 +1,9 @@ { - "description": "Module to access intelmqs eventdb." + "description": "Module to access intelmqs eventdb.", + "logo": "logos/intelmq.png", + "requirements": ["psycopg2: Python library to support PostgreSQL", "An access to the IntelMQ database (username, password, hostname and database reference)"], + "input": "A hostname, domain, IP address or AS attribute.", + "output": "Text giving information about the input using IntelMQ database.", + "references": ["https://github.com/certtools/intelmq", "https://intelmq.readthedocs.io/en/latest/Developers-Guide/"], + "features": "/!\\ EXPERIMENTAL MODULE, some features may not work /!\\\n\nThis module takes a domain, hostname, IP address or Autonomous system MISP attribute as input to query the IntelMQ database. The result of the query gives then additional information about the input." } diff --git a/doc/expansion/ipasn.json b/doc/expansion/ipasn.json index 1ab9cdd..aa9d0b1 100644 --- a/doc/expansion/ipasn.json +++ b/doc/expansion/ipasn.json @@ -1,3 +1,8 @@ { - "description": "Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git)." + "description": "Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git).", + "requirements": ["ipasn_redis: Python library to access IP-ASN-history instance via redis", "An IP-ASN-history instance information (host, port and database index)"], + "input": "An IP address MISP attribute.", + "output": "Text describing additional information about the input after a query on the IP-ASN-history database.", + "references": ["https://www.circl.lu/services/ip-asn-history/"], + "features": "This module takes an IP address attribute as input and queries the CIRCL IP ASN service to get additional information about the input." } diff --git a/doc/expansion/iprep.json b/doc/expansion/iprep.json index 343ce4d..95250e0 100644 --- a/doc/expansion/iprep.json +++ b/doc/expansion/iprep.json @@ -1,3 +1,8 @@ { - "description": "Module to query IPRep data for IP addresses." + "description": "Module to query IPRep data for IP addresses.", + "requirements": ["An access to the packetmail API (apikey)"], + "input": "An IP address MISP attribute.", + "output": "Text describing additional information about the input after a query on the IPRep API.", + "references": ["https://github.com/mahesh557/packetmail"], + "features": "This module takes an IP address attribute as input and queries the database from packetmail.net to get some information about the reputation of the IP." } diff --git a/doc/expansion/onyphe.json b/doc/expansion/onyphe.json index 4c00866..04ebdd3 100644 --- a/doc/expansion/onyphe.json +++ b/doc/expansion/onyphe.json @@ -1,4 +1,9 @@ { "description": "Module to process a query on Onyphe.", - "logo": "logos/onyphe.jpg" + "logo": "logos/onyphe.jpg", + "requirements": ["onyphe python library", "An access to the Onyphe API (apikey)"], + "input": "A domain, hostname or IP address MISP attribute.", + "output": "MISP attributes fetched from the Onyphe query.", + "references": ["https://www.onyphe.io/", "https://github.com/sebdraven/pyonyphe"], + "features": "This module takes a domain, hostname, or IP address attribute as input in order to query the Onyphe API. Data fetched from the query is then parsed and MISP attributes are extracted." } diff --git a/doc/expansion/onyphe_full.json b/doc/expansion/onyphe_full.json index 15f07f1..4b722fa 100644 --- a/doc/expansion/onyphe_full.json +++ b/doc/expansion/onyphe_full.json @@ -1,4 +1,9 @@ { "description": "Module to process a full query on Onyphe.", - "logo": "logos/onyphe.jpg" + "logo": "logos/onyphe.jpg", + "requirements": ["onyphe python library", "An access to the Onyphe API (apikey)"], + "input": "A domain, hostname or IP address MISP attribute.", + "output": "MISP attributes fetched from the Onyphe query.", + "references": ["https://www.onyphe.io/", "https://github.com/sebdraven/pyonyphe"], + "features": "This module takes a domain, hostname, or IP address attribute as input in order to query the Onyphe API. Data fetched from the query is then parsed and MISP attributes are extracted.\n\nThe parsing is here more advanced than the one on onyphe module, and is returning more attributes, since more fields of the query result are watched and parsed." } From 547985b8ce75e4df7ab6b779814a5c33c86ee095 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 20 Nov 2018 10:15:38 +0100 Subject: [PATCH 271/401] fix: Added Macaddress.io module in the init list --- doc/expansion/macaddress_io.json | 9 +++++++++ doc/logos/macaddress_io.png | Bin 0 -> 1854 bytes misp_modules/modules/expansion/__init__.py | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 doc/expansion/macaddress_io.json create mode 100644 doc/logos/macaddress_io.png diff --git a/doc/expansion/macaddress_io.json b/doc/expansion/macaddress_io.json new file mode 100644 index 0000000..6bd2658 --- /dev/null +++ b/doc/expansion/macaddress_io.json @@ -0,0 +1,9 @@ +{ + "description": "MISP hover module for macaddress.io", + "logo": "logos/macaddress_io.png", + "requirements": ["maclookup: macaddress.io python library", "An access to the macaddress.io API (apikey)"], + "input": "MAC address MISP attribute.", + "output": "Text containing information on the MAC address fetched from a query on macaddress.io.", + "references": ["https://macaddress.io/", "https://github.com/CodeLineFi/maclookup-python"], + "features": "This module takes a MAC address attribute as input and queries macaddress.io for additional information.\n\nThis information contains data about:\n- MAC address details\n- Vendor details\n- Block details" +} diff --git a/doc/logos/macaddress_io.png b/doc/logos/macaddress_io.png new file mode 100644 index 0000000000000000000000000000000000000000..e77f45521bd025b92eaa6e31d25f39ab446bb43c GIT binary patch literal 1854 zcmZXVX*d*W8^;+lV;e(c-?EK0(c9RvB>QeKF=85plO|@6rOahy?CVg9k}X?OF_xsp z7-KC5FNHW5bVeFkI%UarMqStY<^Ay7_x=Ap|NnJA-=1`LS34mA838UXE+GedYY$Fe zKRSFooXVdmUB+n{?#^B|oUYOlrrZ&hv|Og#fm5M|aim6f1ZP&I;Z!&%D$&Na=bTNl#(oQ=)KbL8~F?%ZsVXN~7Y z`-)~4wRsdbPKli9`Pa#ZTYr$!#Xsga5xb*S`X4}o_iv`mi9;h_I|)}}mlz^620PUD zJK6SONvdM&nw$SE%xW{OZ-+Pfocn1!PAo*M*xZ%rEjDfYoYmD+%bTKaBL3uESj5S1!9B(N2r5BD3yr$&+#mtw}H4Fy9-6XX|c9UiBy z@v75ZS~WwIXBNPHslMy+Zd-g$p4n2ZXHKe$hL$X4p1JK_JFxqOBzB%z-?R1kKf4g! zYC7TZ;ALjW9)aQu$q^5^mh5=;E81tR_>nP>k6u<>Wutx9u;*$*&LGYkt`S&$e(_)A2*y;U3=}Dg1}NCMEklql3?R2YkPyXBS+&b_gWq zW|~V^C_pOTGKDMRv_143 zV@%v!w!hg#p8#J4r(TT(i zM)xHPo_3b4e{J9s6WXvkg_&ugw_{0B6JYjpp>hBJzu2N;EM$Zu-mQ+BwYL@s_vX39 zNZY(1Ez=L)F@uWr-e~5?mWD0jazL{Us_4<|dQ@8GKlp_Mmvu@m7QK>REF0H0PSgD8 zi2vHWL&9vSF(hB}4>(6}E60U9blvRrwyZRXy<3*m6LnDIeOkHva}s3%8M^*0$aJdeVJZVQyg zEQ7X9_7I(twF0igBC+MyPKg-Rc!dm{Xh0m_1Lh4@YelDs?o1L>`Uj9=Z>+Mh#Ke)D zx-6+Am}8=giNY|3Yft5$m%BzI?M#gZG4z+t7v zG73nQ@JfZ(oAId}&2^BrbU_#(%H7ol-FA2+F%#{2P5|~O(d!mzKZ6Bl= z*sGOTs-#~|;a4p&_LS4Nv%y33S)N71?pNZYlO~@$3fZ^S`?IQae%GQqayH+i-$U$8 zg8lr&t4$EjSVxrn0AqMpxR+96B4`#TeA^pDA5GuTth%FgT?TiHj%u}g4h#*p#1w-P(%YYZr~effj4 z%%oBwCXpXjh>5HgbJn8+#he=~$Br}yPL?fm^bx#1a^sgNk>B8|$&rZzWzRhNolF#Z z;QA}6M+dvmU`AvRl=%*@yB>Jk>_*`2v+k5m0TmSUm;j0i@I)~+BT!7a79D(cfd>9G z(9{7V+-WTu3n;q_)a9Mxn%|EjRw9(OT;~To-Pi>O!PYxqnNfSmot~$JZSO13gn+ATexSN^1Gtbdfh--TJjp!T5xi@u(HDQSIwwxgLaGF>l=8g0OWGNui(nwGCul(cX0@ODff9$$ViVdXc4%c*B) V4l6eAalRfd2OC%G=jSnr{{q-pV|D-l literal 0 HcmV?d00001 diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 1534fda..daed1ca 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', 'btc_steroids', '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', 'yara_query'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'btc_steroids', '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', 'yara_query', 'macaddress_io'] From 627420ca43ce7ba993fe2e56b88c530b49dc89d2 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 20 Nov 2018 10:43:17 +0100 Subject: [PATCH 272/401] fix: Updated rbl module result format - More readable as str than dumped json --- misp_modules/modules/expansion/rbl.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/misp_modules/modules/expansion/rbl.py b/misp_modules/modules/expansion/rbl.py index 6626760..bcdcec3 100644 --- a/misp_modules/modules/expansion/rbl.py +++ b/misp_modules/modules/expansion/rbl.py @@ -89,19 +89,17 @@ def handler(q=False): return misperrors listed = [] info = [] + ipRev = '.'.join(ip.split('.')[::-1]) for rbl in rbls: - ipRev = '.'.join(ip.split('.')[::-1]) query = '{}.{}'.format(ipRev, rbl) try: txt = resolver.query(query,'TXT') listed.append(query) - info.append(str(txt[0])) + info.append([str(t) for t in txt]) except Exception: continue - result = {} - for l, i in zip(listed, info): - result[l] = i - return {'results': [{'types': mispattributes.get('output'), 'values': json.dumps(result)}]} + result = "\n".join(["{}: {}".format(l, " - ".join(i)) for l, i in zip(listed, info)]) + return {'results': [{'types': mispattributes.get('output'), 'values': result}]} def introspection(): return mispattributes From 7cfc7a730bbf8c63d0ca7d1283c81c76ad198874 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 20 Nov 2018 11:17:58 +0100 Subject: [PATCH 273/401] fix: Cleaned up not used variables --- misp_modules/modules/expansion/securitytrails.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 325fa13..7df2c87 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -45,11 +45,6 @@ def handler(q=False): 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) From 36998c53909b175090b84bd056b07454ab0667bc Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 21 Nov 2018 11:24:30 +0100 Subject: [PATCH 274/401] add: Completed documentation for expansion modules --- doc/expansion/otx.json | 7 ++++++- doc/expansion/passivetotal.json | 9 +++++++-- doc/expansion/rbl.json | 6 +++++- doc/expansion/reversedns.json | 6 +++++- doc/expansion/securitytrails.json | 9 +++++++++ doc/expansion/shodan.json | 7 ++++++- doc/expansion/sigma_queries.json | 9 +++++++++ doc/expansion/sigma_syntax_validator.json | 9 +++++++++ doc/expansion/sourcecache.json | 7 ++++++- .../stix2_pattern_syntax_validator.json | 9 +++++++++ doc/expansion/threatcrowd.json | 6 +++++- doc/expansion/threatminer.json | 6 +++++- doc/expansion/urlscan.json | 9 +++++++++ doc/expansion/virustotal.json | 7 ++++++- doc/expansion/vmray_submit.json | 7 ++++++- doc/expansion/vulndb.json | 7 ++++++- doc/expansion/vulners.json | 9 +++++++++ doc/expansion/whois.json | 6 +++++- doc/expansion/wiki.json | 7 ++++++- doc/expansion/xforceexchange.json | 7 ++++++- doc/logos/{Sigma.png => sigma.png} | Bin doc/logos/urlscan.jpg | Bin 0 -> 13553 bytes doc/logos/vulners.png | Bin 0 -> 3942 bytes 23 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 doc/expansion/securitytrails.json create mode 100644 doc/expansion/sigma_queries.json create mode 100644 doc/expansion/sigma_syntax_validator.json create mode 100644 doc/expansion/stix2_pattern_syntax_validator.json create mode 100644 doc/expansion/urlscan.json create mode 100644 doc/expansion/vulners.json rename doc/logos/{Sigma.png => sigma.png} (100%) create mode 100644 doc/logos/urlscan.jpg create mode 100644 doc/logos/vulners.png diff --git a/doc/expansion/otx.json b/doc/expansion/otx.json index 16ee6d6..c6032cc 100644 --- a/doc/expansion/otx.json +++ b/doc/expansion/otx.json @@ -1,4 +1,9 @@ { "description": "Module to get information from AlienVault OTX.", - "logo": "logos/otx.png" + "logo": "logos/otx.png", + "requirements": ["An access to the OTX API (apikey)"], + "input": "A MISP attribute included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- sha512", + "output": "MISP attributes mapped from the result of the query on OTX, included in the following list:\n- domain\n- ip-src\n- ip-dst\n- text\n- md5\n- sha1\n- sha256\n- sha512\n- email", + "references": ["https://www.alienvault.com/open-threat-exchange"], + "features": "This module takes a MISP attribute as input to query the OTX Alienvault API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes." } diff --git a/doc/expansion/passivetotal.json b/doc/expansion/passivetotal.json index 5b09f56..ef8b044 100644 --- a/doc/expansion/passivetotal.json +++ b/doc/expansion/passivetotal.json @@ -1,4 +1,9 @@ { - "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" + "description": "", + "logo": "logos/passivetotal.png", + "requirements": ["Passivetotal python library", "An access to the PassiveTotal API (apikey)"], + "input": "A MISP attribute included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- x509-fingerprint-sha1\n- email-src\n- email-dst\n- target-email\n- whois-registrant-email\n- whois-registrant-phone\n- text\n- whois-registrant-name\n- whois-registrar\n- whois-creation-date", + "output": "MISP attributes mapped from the result of the query on PassiveTotal, included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- x509-fingerprint-sha1\n- email-src\n- email-dst\n- target-email\n- whois-registrant-email\n- whois-registrant-phone\n- text\n- whois-registrant-name\n- whois-registrar\n- whois-creation-date\n- md5\n- sha1\n- sha256\n- link", + "references": ["https://www.passivetotal.org/register"], + "features": "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" } diff --git a/doc/expansion/rbl.json b/doc/expansion/rbl.json index 0f67c2c..9700eca 100644 --- a/doc/expansion/rbl.json +++ b/doc/expansion/rbl.json @@ -1,4 +1,8 @@ { "description": "Module to check an IPv4 address against known RBLs.", - "requirements": ["dnspython3"] + "requirements": ["dnspython3: DNS python3 library"], + "input": "IP address attribute.", + "output": "Text with additional data from Real-time Blackhost Lists about the IP address.", + "references": ["[RBLs list](https://github.com/MISP/misp-modules/blob/8817de476572a10a9c9d03258ec81ca70f3d926d/misp_modules/modules/expansion/rbl.py#L20)"], + "features": "This module takes an IP address attribute as input and queries multiple know Real-time Blackhost Lists to check if they have already seen this IP address.\n\nWe display then all the information we get from those different sources." } diff --git a/doc/expansion/reversedns.json b/doc/expansion/reversedns.json index 96773ac..6934462 100644 --- a/doc/expansion/reversedns.json +++ b/doc/expansion/reversedns.json @@ -1,3 +1,7 @@ { - "description": "Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes." + "description": "Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes.", + "requirements": ["DNS python library"], + "input": "An IP address attribute.", + "output": "Hostname attribute the input is resolved into.", + "features": "The module takes an IP address as input and tries to find the hostname this IP address is resolved into.\n\nThe address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8).\n\nPlease note that composite MISP attributes containing IP addresses are supported as well." } diff --git a/doc/expansion/securitytrails.json b/doc/expansion/securitytrails.json new file mode 100644 index 0000000..8541e4e --- /dev/null +++ b/doc/expansion/securitytrails.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion modules for SecurityTrails.", + "logo": "logos/securitytrails.png", + "requirements": ["dnstrails python library", "An access to the SecurityTrails API (apikey)"], + "input": "A domain, hostname or IP address attribute.", + "output": "MISP attributes resulting from the query on SecurityTrails API, included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- dns-soa-email\n- whois-registrant-email\n- whois-registrant-phone\n- whois-registrant-name\n- whois-registrar\n- whois-creation-date\n- domain", + "references": ["https://securitytrails.com/"], + "features": "The module takes a domain, hostname or IP address attribute as input and queries the SecurityTrails API with it.\n\nMultiple parsing operations are then processed on the result of the query to extract a much information as possible.\n\nFrom this data extracted are then mapped MISP attributes." +} diff --git a/doc/expansion/shodan.json b/doc/expansion/shodan.json index 734d768..57241f0 100644 --- a/doc/expansion/shodan.json +++ b/doc/expansion/shodan.json @@ -1,4 +1,9 @@ { "description": "Module to query on Shodan.", - "logo": "logos/shodan.png" + "logo": "logos/shodan.png", + "requirements": ["shodan python library", "An access to the Shodan API (apikey)"], + "input": "An IP address MISP attribute.", + "output": "Text with additional data about the input, resulting from the query on Shodan.", + "references": ["https://www.shodan.io/"], + "features": "The module takes an IP address as input and queries the Shodan API to get some additional data about it." } diff --git a/doc/expansion/sigma_queries.json b/doc/expansion/sigma_queries.json new file mode 100644 index 0000000..f127ba4 --- /dev/null +++ b/doc/expansion/sigma_queries.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion hover module to display the result of sigma queries.", + "logo": "logos/sigma.png", + "requirements": ["Sigma python library"], + "input": "A Sigma attribute.", + "output": "Text displaying results of queries on the Sigma attribute.", + "references": ["https://github.com/Neo23x0/sigma/wiki"], + "features": "This module takes a Sigma rule attribute as input and tries all the different queries available to convert it into different formats recognized by SIEMs." +} diff --git a/doc/expansion/sigma_syntax_validator.json b/doc/expansion/sigma_syntax_validator.json new file mode 100644 index 0000000..8e17ae0 --- /dev/null +++ b/doc/expansion/sigma_syntax_validator.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion hover module to perform a syntax check on sigma rules.", + "logo": "logos/sigma.png", + "requirements": ["Sigma python library", "Yaml python library"], + "input": "A Sigma attribute.", + "output": "Text describing the validity of the Sigma rule.", + "references": ["https://github.com/Neo23x0/sigma/wiki"], + "features": "This module takes a Sigma rule attribute as input and performs a syntax check on it.\n\nIt displays then that the rule is valid if it is the case, and the error related to the rule otherwise." +} diff --git a/doc/expansion/sourcecache.json b/doc/expansion/sourcecache.json index 13c2a03..ab4669c 100644 --- a/doc/expansion/sourcecache.json +++ b/doc/expansion/sourcecache.json @@ -1,3 +1,8 @@ { - "description": "Module to cache web pages of analysis reports, OSINT sources. The module returns a link of the cached page." + "description": "Module to cache web pages of analysis reports, OSINT sources. The module returns a link of the cached page.", + "requirements": ["urlarchiver: python library to fetch and archive URL on the file-system"], + "input": "A link or url attribute.", + "output": "A malware-sample attribute describing the cached page.", + "references": ["https://github.com/adulau/url_archiver"], + "features": "This module takes a link or url attribute as input and caches the related web page. It returns then a link of the cached page." } diff --git a/doc/expansion/stix2_pattern_syntax_validator.json b/doc/expansion/stix2_pattern_syntax_validator.json new file mode 100644 index 0000000..2ea43b5 --- /dev/null +++ b/doc/expansion/stix2_pattern_syntax_validator.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion hover module to perform a syntax check on stix2 patterns.", + "logo": "logos/stix.png", + "requirements": ["stix2patterns python library"], + "input": "A STIX2 pattern attribute.", + "output": "Text describing the validity of the STIX2 pattern.", + "references": ["[STIX2.0 patterning specifications](http://docs.oasis-open.org/cti/stix/v2.0/cs01/part5-stix-patterning/stix-v2.0-cs01-part5-stix-patterning.html)"], + "features": "This module takes a STIX2 pattern attribute as input and performs a syntax check on it.\n\nIt displays then that the rule is valid if it is the case, and the error related to the rule otherwise." +} diff --git a/doc/expansion/threatcrowd.json b/doc/expansion/threatcrowd.json index 83af5fd..99725b8 100644 --- a/doc/expansion/threatcrowd.json +++ b/doc/expansion/threatcrowd.json @@ -1,4 +1,8 @@ { "description": "Module to get information from ThreatCrowd.", - "logo": "logos/threatcrowd.png" + "logo": "logos/threatcrowd.png", + "input": "A MISP attribute included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- sha512\n- whois-registrant-email", + "output": "MISP attributes mapped from the result of the query on ThreatCrowd, included in the following list:\n- domain\n- ip-src\n- ip-dst\n- text\n- md5\n- sha1\n- sha256\n- sha512\n- hostname\n- whois-registrant-email", + "references": ["https://www.threatcrowd.org/"], + "features": "This module takes a MISP attribute as input and queries ThreatCrowd with it.\n\nThe result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute." } diff --git a/doc/expansion/threatminer.json b/doc/expansion/threatminer.json index da75784..d2f26bd 100644 --- a/doc/expansion/threatminer.json +++ b/doc/expansion/threatminer.json @@ -1,4 +1,8 @@ { "description": "Module to get information from ThreatMiner.", - "logo": "logos/threatminer.png" + "logo": "logos/threatminer.png", + "input": "A MISP attribute included in the following list:\n- hostname\n- domain\n- ip-src\n- ip-dst\n- md5\n- sha1\n- sha256\n- sha512", + "output": "MISP attributes mapped from the result of the query on ThreatMiner, included in the following list:\n- domain\n- ip-src\n- ip-dst\n- text\n- md5\n- sha1\n- sha256\n- sha512\n- ssdeep\n- authentihash\n- filename\n- whois-registrant-email\n- url\n- link", + "references": ["https://www.threatminer.org/"], + "features": "This module takes a MISP attribute as input and queries ThreatMiner with it.\n\nThe result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute." } diff --git a/doc/expansion/urlscan.json b/doc/expansion/urlscan.json new file mode 100644 index 0000000..d847761 --- /dev/null +++ b/doc/expansion/urlscan.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion module to query urlscan.io.", + "logo": "logos/urlscan.jpg", + "requirements": ["An access to the urlscan.io API"], + "input": "A domain, hostname or url attribute.", + "output": "MISP attributes mapped from the result of the query on urlscan.io.", + "references": ["https://urlscan.io/"], + "features": "This module takes a MISP attribute as input and queries urlscan.io with it.\n\nThe result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute." +} diff --git a/doc/expansion/virustotal.json b/doc/expansion/virustotal.json index 8c203eb..9008003 100644 --- a/doc/expansion/virustotal.json +++ b/doc/expansion/virustotal.json @@ -1,4 +1,9 @@ { "description": "Module to get information from virustotal.", - "logo": "logos/virustotal.png" + "logo": "logos/virustotal.png", + "requirements": ["An access to the VirusTotal API (apikey)"], + "input": "A domain, hash (md5, sha1, sha256 or sha512), hostname or IP address attribute.", + "output": "MISP attributes mapped from the rersult of the query on VirusTotal API.", + "references": ["https://www.virustotal.com/"], + "features": "This module takes a MISP attribute as input and queries the VirusTotal API with it, in order to get additional data on the input attribute.\n\nMultiple recursive requests on the API can then be processed on some attributes found in the first request. A limit can be set to restrict the number of values to query again, and at the same time the number of request submitted to the API.\n\nThis limit is important because the default user VirusTotal apikey only allows to process a certain nunmber of queries per minute. As a consequence it is recommended to have a larger number of requests or a private apikey.\n\nData is then mapped into MISP attributes." } diff --git a/doc/expansion/vmray_submit.json b/doc/expansion/vmray_submit.json index b977203..ea6cf3f 100644 --- a/doc/expansion/vmray_submit.json +++ b/doc/expansion/vmray_submit.json @@ -1,4 +1,9 @@ { "description": "Module to submit a sample to VMRay.", - "logo": "logos/vmray.png" + "logo": "logos/vmray.png", + "requirements": ["An access to the VMRay API (apikey & url)"], + "input": "An attachment or malware-sample attribute.", + "output": "MISP attributes mapped from the result of the query on VMRay API, included in the following list:\n- text\n- sha1\n- sha256\n- md5\n- link", + "references": ["https://www.vmray.com/"], + "features": "This module takes an attachment or malware-sample attribute as input to query the VMRay API.\n\nThe sample contained within the attribute in then enriched with data from VMRay mapped into MISP attributes." } diff --git a/doc/expansion/vulndb.json b/doc/expansion/vulndb.json index a4fec3b..330a3eb 100644 --- a/doc/expansion/vulndb.json +++ b/doc/expansion/vulndb.json @@ -1,4 +1,9 @@ { "description": "Module to query VulnDB (RiskBasedSecurity.com).", - "logo": "logos/vulndb.png" + "logo": "logos/vulndb.png", + "requirements": ["An access to the VulnDB API (apikey, apisecret)"], + "input": "A vulnerability attribute.", + "output": "Additional data enriching the CVE input, fetched from VulnDB.", + "references": ["https://vulndb.cyberriskanalytics.com/"], + "features": "This module takes a vulnerability attribute as input and queries VulnDB in order to get some additional data about it.\n\nThe API gives the result of the query which can be displayed in the screen, and/or mapped into MISP attributes to add in the event." } diff --git a/doc/expansion/vulners.json b/doc/expansion/vulners.json new file mode 100644 index 0000000..f3f3026 --- /dev/null +++ b/doc/expansion/vulners.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion hover module to expand information about CVE id using Vulners API.", + "logo": "logos/vulners.png", + "requirements": ["Vulners python library", "An access to the Vulners API"], + "input": "A vulnerability attribute.", + "output": "Text giving additional information about the CVE in input.", + "references": ["https://vulners.com/"], + "features": "This module takes a vulnerability attribute as input and queries the Vulners API in order to get some additional data about it.\n\nThe API then returns details about the vulnerability." +} diff --git a/doc/expansion/whois.json b/doc/expansion/whois.json index 7c5c119..938bad5 100644 --- a/doc/expansion/whois.json +++ b/doc/expansion/whois.json @@ -1,4 +1,8 @@ { "description": "Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd).", - "requirements": ["uwhois"] + "requirements": ["uwhois: A whois python library"], + "input": "A domain or IP address attribute.", + "output": "Text describing the result of a whois request for the input value.", + "references": ["https://github.com/rafiot/uwhoisd"], + "features": "This module takes a domain or IP address attribute as input and queries a 'Univseral Whois proxy server' to get the correct details of the Whois query on the input value (check the references for more details about this whois server)." } diff --git a/doc/expansion/wiki.json b/doc/expansion/wiki.json index 14c4451..d6de62b 100644 --- a/doc/expansion/wiki.json +++ b/doc/expansion/wiki.json @@ -1,4 +1,9 @@ { "description": "An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis.", - "logo": "logos/wikidata.png" + "logo": "logos/wikidata.png", + "requirements": ["SPARQLWrapper python library"], + "input": "Text attribute.", + "output": "Text attribute.", + "references": ["https://www.wikidata.org"], + "features": "This module takes a text attribute as input and queries the Wikidata API. If the text attribute is clear enough to define a specific term, the API returns a wikidata link in response." } diff --git a/doc/expansion/xforceexchange.json b/doc/expansion/xforceexchange.json index 13d3622..bbe3c86 100644 --- a/doc/expansion/xforceexchange.json +++ b/doc/expansion/xforceexchange.json @@ -1,4 +1,9 @@ { "description": "An expansion module for IBM X-Force Exchange.", - "logo": "logos/xforce.png" + "logo": "logos/xforce.png", + "requirements": ["An access to the X-Force API (apikey)"], + "input": "A MISP attribute included in the following list:\n- ip-src\n- ip-dst\n- vulnerability\n- md5\n- sha1\n- sha256", + "output": "MISP attributes mapped from the result of the query on X-Force Exchange.", + "references": ["https://exchange.xforce.ibmcloud.com/"], + "features": "This module takes a MISP attribute as input to query the X-Force API. The API returns then additional information known in their threats data, that is mapped into MISP attributes." } diff --git a/doc/logos/Sigma.png b/doc/logos/sigma.png similarity index 100% rename from doc/logos/Sigma.png rename to doc/logos/sigma.png diff --git a/doc/logos/urlscan.jpg b/doc/logos/urlscan.jpg new file mode 100644 index 0000000000000000000000000000000000000000..52e24e24592886b5f18f5e2c64d4723a9042c9d2 GIT binary patch literal 13553 zcmbumWmFwa(=I%?y99!}ySpa1yChg3xLa^{C%C%=3GVJ5+ycSfUBlTV_kEt{UF*C* zzV*%C%+$WRs=B+TtERfAd0BW_15l;kOT7oczyJUY^Z{Nrz_X>q#Pk%E72Zq9zWY-E z9%E@_##4u|B0)Po10|Wq(fsuo)h@zq_@V_26bFbO~V4C4o z*Z<1)|1LoIWb9xBY9K7AnA^zK-VsC#gJ^jdN849A21H{TnHqco(fJ^n-X7FI5dGs7 zH~dR)z0#lm(s!@4y{fVp0DvQcXyVU*Y5G^%@Gt$^7h)q*dn*u+C5R@svUCEq;m`5f z6GUTcH5Jem|IeQTpbSU=q5v^q2si;|fFb&_U;iU-^A%*y6A&KrAB;Q$08sn^puYV-m|+0`G=OZ3 zvubN#Z}6uc2+$ea!~_5?iU0sf3ji=cwuaaF|6Bgo-XPtt`hmQ808n)Y0NF7BNJ#|% zN>F_$x-ZLs7ytzU2?+@S1v)`NK|#YH!oq+K6a)k~L^Kq1bTkw+Gz@I~HyD_2vCz

4Z5MZPL zI2srP8rVxOXh47g;1FQ19`)}51q}fS0}ciYDt!yWL8bqeyj~%pz`!A(UzPww5DtzC zfeN~fN7?_)@jqw?bvF5$(u(>xMsvOomGIW%OWKNY?q>^l7yr#BsP1_9IXFk6x3NxS zFHb9&2ULj8R`*77MOq)Kxw3DGJ(-Qqy~K{49!UuiIoNgl+A)bZlE#i~n#^gr-GPzTEdp3Z)^ zKnS>u6`6B|^3?wR@*I2Eoltyvle$sGF`omzry#UI0#8*X`U1;$kQDj{t{AzYZ~F>a zmaU+{3INpDgm&;p#H&6JE!UwX#ti(8Hl6RbsVg4F>!CAuI1yP?tWT8h&HFxlyCx~p z2MI802y)F>@?Rw@|19x;Sh!~)a?$Y zTwU;cFv3NSJToG^;Y2vKlaB+J^$VvxCMJ6GjCF&A{(8$;UDHV!3^#11`}`1967js; zH{aBn6*$YUy0Agc0>EXa^f#9s;DT|2+%S8N)2ZY+#Ox=6Kn1e>@?fjG?roWg>H^X5qnh}c#C%p zGHsQ@kq`JY4HlilCKjKuUhPLqN>K><02SPoDPSCMz2Os_YwyK_l3!Gb7ObXi(*)^2 z>3}g9!eDUN@V5d~PB5)8N)0P#0jMdr6o-Zj>QL{D9NU|n^aYaF`^#Bp<#s$7Kr<}2 zocg}DlTLsc1Okq91k%eETmH4em~_YR0+G7vZnnVn49^>}ED?6fDV3%dz;-*1Oa%D} z#4O-6d_2dVy}2xtqWSH5HJUjT6fFVZjZ7r(rr9<`D>0_Um{`DpoJ$PFZp1^|Q~=GSDp=Oq_6D1U=D?UZ?oKG)D-893mdY2AaJ! zaRy(NhFjSW{An20A#zkklW@_EFfg}xpJBu_qw9J2$QtWZb-5b!gu_-HeoH#e@6IuZ z!uvDJ(rQNj+@Jer^>hP?t~Q~-^h0OAFaUP7Kece7dzZZ|3~Z&=yY&fGA@TXZM8YxQ znGtjpZRUUUH`@G?KX(Je6L$>1SMs!+Q6~{LU-_%9S3?89eO|{U?|U;T!&CV7_=u(pgE?kwT<%aeB5Sl=xX7mE-EeP0 zyw``d?=C^2FZ@Y0$Pr)VK#!I&f)^lVicMbPjjP&KeMUpd>|;$wWWIAuuMY{TfW*1@aD+D?SJ#jAg&~*r~Yf$)`!1|%yE zssSE!o&BAEU~mm9(i4n}8q;=9N)?|!5a{Glwz%Z9t@NBoNHM^wy_To77iABE;c!o4 zOVJw^dDNU9!5FhScmCxEUBSNLaZi_PXzxtDBXG5-<1(3Amek@uiS4o0|1YlpLB2i+ z=urVMNHB0{Z~*3?hXDi_I3yH6%nXf+hK?bONrFX6#=^?3Xv+Z#{;(h(FevaBAOTw# zD!MM{FuXkv!_w#^sHblnAywJ<5ma8o@A!H^-y^M ze74``xA;U;$i|XQh8s`{iq(O&E#8Pk#%*URKg1MeGKyrvNgbgbYOQypsIuu`+OJ($>ZOq7$J1}xc#aQ|S>T%Kg;pOD1 ztHr*%t(B65^8%}L#LUk9vglnQ9{YEg*ohLEH+34Qni1BHvd6MY4fqs&55qb|a7s!l zUwzrT>F`;tr=%`>7xnVfy<_R*Y(3DvpdI5J@3wm+qI}h?6r^x+wnR|zf33YgE%kmsVaYR|~JpnIQ@ZR5Ifxn)p* zQ&Jo{8~di2itco`IeaPC#r4__;!)qc1{Gr2|KDdTd8;=$+v~kN zi~5VOCktKtlyF%|w=TAwTwWz|Ed*uRJ;>WiSK_%%;(0&cBg7*CO?ed}C(Yciqd@{q z^5EPX=>+$=&W_ZpJ-0|*ZTPM_{+I{HlKJQm0nI7bNjo(ve7s+Ur1u`hF1dKe#&*gp zs)E91Yi?_=R;6~l5Ug6xveWR{!3h64T#mj8x}9GR#X)nq1uBDzj6ig!34djk&_CN|d2+oNo%#n~nbR6c*Wsw)xM~d*Sl_%oyBKuMkgk{f55E!%|_fT)Yd5{$UO? zfNq@e*e)u^K{v{JO!BE74_}mVQ3u(a2Y=Rv1=CMSXVirCQV1WJVXf*mphs(rlJ;9t zO5aKa%Z$8H?AXG3mE}tC7>rr>lDJxbB+K#fmYs`5gcCyQmV?tg%x2GmUTr!`G@Y5! z8%)hSXUC786}=c$(5n`=(Ka?@;VrsG#p&U*?9=`eToxflF2Qqpt?uq$@INdauFUY7 zk-3su2^8;)M^)uNjQk^^X6Pk{#o{GfK9Zb8Dpb+odgFY--BL^Hq*?wxI1O|+tCZ=j zmIuAi-40(RNJELjU^c++Sn85dmNO6qE?Dvj0wl!$3htvjex^;9p)L*y?O^0157M=N z&-JR2)&b{!zSpA$QWmxiw@QLqc{7g1Gb%JBBz;Ysyt*IQ44+QGSAHjmW+=2&QAu6^ zB%?j#8%d75Eec-%g@J?2#*mQA-iIGUL$(uGo|1|vbi`edsyDM}LyYO5 zI1$>|`cQtlXSLSa4_+=>7@;>H#(*cGbnQ((xYb=uZTf|Uuu1w^c z^DD&q6pTtg#9jJ=qsRDu)j#yj$0{ZARH)?ruF^i~+6twOdfJZe@czTdosTsm2ls*< z1to!HZMA=4%$Tq3rlxz17>vdy;0rI$!1(mNKp>;6sK2>|zsFy;#)C30-5g-N-b55=n?uxXdoOg=%QTNTJCn-%9dZvHLV0F4#?j_u$V0sO_ zA0tP;?lQcz_45Np_cVNe(mBLCtxOMDLWvAH(w}DqY_Yg)4_HK@8qb?=&1$P>Nt6|>Pk8tgnOR@TAKbNn3MBcp3P_vGQp+)GWdIW$4mJzrL&{OT4e$(o z0hVEl_}wppO2+b1n|ek`W#W4vSHc z3Rs6LHFofVOvzwlYJzU_QJGmCY1evF!+vhXE%U-OA-pKR0ezu4LT(nmjln6^vtRCqL2BdCTF_34gLxef=ZJd(irNz= z$$a8|^TbxnW5`l57M$;;#Hs_~nK=f`PGIZCE!~v@$hl}i&IJJu1pxu|?_~FN;tToe zTO=$P%u1weqL{40BFc(?dpaJwX|BD89V=X@ zRE*tSX%f7rEP5JGWz-%F$ulNQxf^VAOWMbe3<)f>DNpnUJ(ck8u-B8 zaFwAfL}ne@)Q)6k_gz+B|9e-}y}J6llF7Sn)8@#zd1EfPiczM*^2hD|*erT7n`7JX zFpH|#`d{#cTJx6`u`d7tD<4~EKbUDZUUn1`-p0CJpyrjHF5~cdu9!)DF4K2P&xK_* zg?e`+t~dR8%e=%~5yV#I{XOtCdq*P25$2w4n9+CZ7%zZ!f#*$k$t~xR%A?WSXv;mL zG8IBXCV%4#-*!l!EQY(T^5)XQ1GlsZLljFR;ugti{J@}fR^i_)O?|oBV3xEcZkdIb z<+R)9GJ;w z)D`pRpJzS|*4j49N{|jqNQ?{ACFLzG<3@VJo>UhZ7|Vk*3z;Y2##C3SovT8yRYfuF znL-Hrc^_b(i2I{p?y^8bjUSX~gM&lEK*GTOv$FCU$I(CojhRIWl@v6{4D2AuSlKW{ z4efnnbJ!LA;&Q9HCNafq9sWtYp@m)(ZLD_M&j{GQiNQhs4syrop)RFuiW{Fl#PPp%0y#4M$5e0P%L@WHfbCmJbb z1C;dL)IK-!4InnY$3rgNrwUZ>d&5`Y&|{U$H^7RQ*Q%CIhLkA99@}jw#jerq99LIN zLH5P(*XLBUzSd(5==Ttw-!5MOJ7yD3b8A9>?zt+w__|5nG287ah{)17`5`&i6WoD( z+bdkF+IVw?P@Yb^8fItGjo%R5jNERB6@2wGU;3MWWR+R))({=t_cMBIDs7&leS7DL zC}m2+e*-U7N7b9Y#=V#-->F_h9$^?;Mb^z)$xNLW(mQw+Hl~5DEE>$6M(I1^sAR|5 z5!YH^G?FfB;Jt^(8#0hmP7(pbC!Dh0n!OnVzyz6qwC!cH!1 z`SASBC8H$UZCr3tsN3k6*hwmC|7LztEqpXmZj)|JBo_6}VK^oe{MS!Vg_!$2w%a<> zjU3Uz38DocQ=seTRpq8^s-yjCud`oKmvTy!+X`Gdbou_Qx(vTag7o5PI4hvlF!?0KJH69II86bq8efT z){ILs5p6;pt`UB&t^IzqEdS!_4{r-#C;R$EI?bHTQme5O^xS?A}8 zofd2Bj5b>28DDctJ(Eo6~wZKd1~69#JfTZ=hW7D z>{|%R*KHmKE|mmQsCvxnVSZyRY1G(adcapHvErzBsh?6DDu%2y#Q=aYIL6-u%5}Za`Si647EQ6BoK;H zt|1mJ4QykGnlVLc&d@4>kxt2)FAVcT+)t*ugyhGwcqpKceK{+}3)6-`$-K1NC|_j{ zbdvQ<==iF&nEF&jo==rss+tSn#1s1Zi9rxq7n-e-#>w^_im~vX=wW#IUzW|N! zQji22CuyZg3_H}w$C-!^b7OqH%2LTKaCqy~>*$Sk?O3|fFz#6}t>@rKMF{i2YF z*&U}pI((We?v_v?v;sR?#2}9!FCgF4V?RZcqBPtdm}q8HeNR32#;rm9UHt$9W_+Z_ zbn83vl<=VCUvzFihq33@Ecab3H68UG*Nmv^m(pvMr>$XtVh#Zkyln&_X$DHi3+x~;kY3-^ibf&4fOF^{0$G^$crlEQphl+7o3}8@# z3pkiZ-WFM!(-BITZRfh;j5EYU*_wNQUAq-(xd;r+-^7$F5yYsyXw0vq;lA8P$FYQ} zdqP&!v32A*<}@fYpMJ+l?warwdKq1Ep_qwafcnZBIar_w3>%4E#ZV$tOW3N4m{Z~X z+Ma!+g;JBw2eQ(6Y)@j&CDVM%J_Jc+`Aw+t368vA*CleuQ5Zgrt=}8Ml2+`&&O1gP zE}=Cc2GwJvVcZesVKN(hpF)zuWQoMf8YEm7lTdpMX5V3+(+=kEy0yD{_OPx%nWT|g zHYQwfzHgz!-*zb;ZcZb$X@(*1{joqhrk>T*(jQZi%U_-4*#E5pwx;%X^5yhJw15zG z1Bje+>LFBPavgA9Qu*QicWyz43R=zqgMxtqy$JZ50D++apv*@^36pEAo{det0=yhIFP!JK%5p?+|a!Fh`y?b56)RD&rnu|Zo`!!l%>F$5T)%Z=@M_Eji zsK$a%m=i_9 zb*dT1h}@+$L^WaRjKT(vx>7N1&kry#=wonXIVriUO2HQZC9`D*pYaa2J~TA~EaMFe%7vCSMI&1QF*FI6 zq2D7BmODSua4(;5=A6!nMLx{#h(|#z>8IH#WKowG4|`M9WcXwH#B9dMdCGdoY-wVe z*bY^;!VrF2-Zz?zpL)kL&~>?n6h1}}Rq6YQO3p5Qyk`F;m32axHUyXTFgL@`e-aqY zSzOCKd(YTpy>l?-LK(f0vwf(`Wv)RuOC>=I&68a$X*RFl_B5to4)~!{y4&K}P0j^2 zy1M+p;Ej!M7NvxEYr;YnU|?=X*1{+AHf%6I%6iG9bt!8h8yuW&;f$xBF_?X z9&Rj;uM>AxBAoipRiU3KwWbqp)Lwwu5KUkI^2Z~s$Kd-$i!f6A;d!3ZojEWc!phKM z2mTM0{PbUOpZn^-(~s2?A_QV{0+?-b85*jU-Q+TthFzTj9qhTDh;C)l%-935q0$5#4dn{-CGSz)NhJ+L6DD<;qJ#TYZ zw6-a!LS$P&Xt>xD`}t6R!v1@1OP!Qwo)cY`FKVFhT#6JJ%V{cdFJ02w;vWg?;~MGc zJpX;1Hs)Pd^Q8m}A#I;p2 zPc|xq^-5`B;!x7hwKmA;tzArq0^I=ZPiV*axR04H0K%JMQ9c%x=4h6ZHHz`y1d@D2 z&i4wP5B00>r1o;7ti2q+$BBKBl){BHMbJso zCpv!>#*FN9=QuagX2*`vKmA0C>#5aHEQ3FC%O$U6sh9-A{xn`0`#p}Yu4)nC9*Txs zr@$Mlu-24;xND_2R27p-pR91*Ns-665f@KU!GQmwx_eMt@)-%kJ zN`vKa7rsgTb)JYBH1ffbKx|W#EZ)#HUUDE#2a@xZV8RD~r^l~NL~W*{$f@Im`h_mH z=Bn+Vyk~iA$7GBk?NXQL#=t<`O2>Ujx^D`=*sMBl79k-oI~f)?AoZn`vc_#pzV z-@@ikH%=(fkDBINVo1r&GDq#oxJ)|VGB7&g7bQ?dTYTbWr^F(|$tebiTl68xzaHwQ zF-zcH( z&`0VNzctotR*(7%TC$#1Ch6D`+XoBfatWfs%&MVyG)A-FMwE?s_wYSA_>1`!(VyDZ zj@jS{(I0 z;OQ$oZ$aozCt~oUjL)WnHZMX zPl|%)|AH?7w;u=seIcy=(Q@|v9|V@T{?{MXC#Zg4T+v=J-LITNf7r79_)o6}6b`6g zWt%|iE;{{CyaYU)xSxE$L2P0m!Etc77ht1_TcAW?{8f_T>2d-Amg(^_JNb6rW$#ro z;BtZomN?_~m+a#&v&$F|;wSS0@NW_OIXt)B{__0x0*JnfQng7DSw~KdoI5Kd%`}ivda@Ru*8AkOiwj0z&b3XBnZL-sXJr#gMV)@(JI;B35bLA zpzvRgfWs2k6#M%Sq80#!pibz#NxKS|Q-!Yk?v5bU1UX_)nssOnO| zPH$i0hBl|yyN@4cfHTH=^!6$2lZD1NuUY;~;|XQD3!9Ez?v%CXdAG242HVYodIzvy z{UID@hRS45N2|C$??#X}h=^JMO#IyLzc2$@h)&uJx`w!5eaa-?oh-T6QTuC}-+q%% zIEPzbBYIqAD&o@gI`1WlGhj8!Q26W$dxNo4Kv#8M|_01q5GxPM#`^TlBh*-BygtcyCf9jaj0q6Ep%B;el zRo@)$A8T?cp6pJ|nJ7OnhWf)>u1n$Iu8Lq6jD_4QSUex^j27B4EhHxwrQJ}r-)WYc zhB1Hp5JqZU6l_K1@Rq*l{VIG`uz^^bU==e7T6`VT_x-Q#PbU;BpJy`K@-_{~adhwL zV@B#|1VUMKety4gJm+WFD7kY+jU1vxx7Qt%>Zg*`g)^pra2u`oez{-fr6QOQE*16j zv8)-r-b6psW`RdZHdKN-Q63p7lzXzy7b~AM%^*MZeuOQhBtQA6tkOs=;o!(T)EnRZ zNNTiUu|g_53llRd}4TUqvXtRMQjHxz|&7Q6K0CWUcE zRcWlTeJv&yC0}}o>K4noN<1i(yQsy!s|E-mw3@$rhlKwEG$p1=Ri@YQq=v*ZWch#m z?RB*FdJ}5!#uhUS?mlcP(~_I+K9APfH#uYSNe_QW>T}gFx3?J;ut7XT={|iM^HrC; zqfQcq4cB7u&d*v{a@+X8#C&^X0;yJo4g<}hz5O=GDL~oaXGqLf__~vU*5*DJ7-cDd zk-yPxm3R(ZQ91rdsM#qPg<}Q2P@Pa`di(bpf~*~vob21)@x6&RK_6X3C)^!jqKrva z77rgWC#Ry{KbL;t4_^(?*!iWms>Is&Saerv&}EplBoupzqNKAA(S@b7(Qz&Waw8fv z(Ap|!PZVga^*?Te`a0WSR;n_v%khn!Tt639?E2$HuS>13UepjE_K;cB1O2;jN@#<1 zMSQ{Y0h**!@JEZwtoH2JFG6U`4Erp`%&tscY57xRRoio3dI4Z2D^TDI>0-NYAuQD^ z+Td0(rpCTknw8OAqA6Qs-tukcXbj!;hggl!$^$J5?@4Wf9r><=W_LJ8VedjlT)^E& zxrp0fp}%V&r4840V%l4wy>%eAMvb^k>=Za(^&0rSUSyUX<``DHmc*oj#MF@J4&}am z&GmM7{uIg&p13+!^)uFbU|!XP{?PG`f}d*|PR>ssyiYEfT$zF1cHxydyNvXRc=Tf? z>+js#pj^{_$)>`0ci)$(ATbPxjbCio?0p%W$BPL`ljlJa3)<_Q3_8t6>%4#NeV@Kj zCD1y^z6!wCfu5jMKY~rcBLHuS6H@k_$|F`cytt5qp(4Dr8cnMjt=#=22tBr}^o%O7 zXQIZiA_@owz}*d7LCB6y;w_ZBF}X*tanp1z%$JFA`32uw zec#4zwtkCtg$Y%dFXr0rY&s14xVQLxGsnjnkcCvtcLz$4h5!XhF zaE0DV?ZZSoMI1XuWG91>s(Ws4NFUWO=a8G}-2~d|>W-+sAZe`P#FF(M+|WV2_=;LL z@+7cPhbPOJ8!}mwC)MU5p^B%Byo=@5FWe`r&NX#=Z^m(v4L_v{g`A>cZ$~2Oma0?` zK(k-`HY?PG_n9|{zg8E5v>k(%y3^D-gE0~MPOF=dy`E`aM`QtN^m`ME840=xBnD`KVmQQ%FId~Nt zdz*%{jcnsIR=U(1iU;P^P+$81f--*4W-$b)DE5j5?xP=Ox*2*Mv}rGZ1KM2AX^$zw zJ+PfwL<@ExD!f5n|z zZr}v(_IBM#ZAGtXW_q;>Xxx0tX=y+WMU5-$)Zl3LyXSDjr&edg02?c0kPPA(o{@CV z{DOHTW#Nxw{yCUOYAF^++|d<4MI%9fA7w%5VRVn*wpJ#Q11(-y<8u_ZGZgx83+6}j z9lcIylaF?y!D)l91bTU6T7+13Wyg3zMKB#qVnlXk&n@A{c;Q>Qa&)PAbVP zFK0sC6)oa{M0d0KZ7C!9%iL<&D6Duad$Ibcd%;th{cQ?+#AiT{uKPy_8QF2Zf{`79 zbtOcvwK@{fRXel~EWj=xbApekWi)1XEi0ws&#osBQ40Bx9@gq&bFI#Pl1=Z4Wlw!%9qRWsH8mOyf z#nb(N@9t&O`k+^+a%-2=rEoI*3bm!9^m&pJ1_MWyrz$~w&*y|vv#Ms`^hy7cum{B; zD1g7tk~@mh-t8gsK?0^5(uVVq_bW4b#3YBWUt`l?2jLcX0kTF*$pP~Bov58BH}8j(he3GpHRi_l0g8LSnGG~A#*9-I;C{>wZv9K11Ags%5N0G|#{?=H z=2r!(zMNt!N?RcFSgatW51J7~;>TPJmW;1imENlEIsLfLJ<;>HB9oDN*#ag{V#P6E zmGVcvg&#ya8y~;bPbbJfK7jXi4)db4?AiR)t9QA|{?m>UAzQ)knK~ktRrc8kS)Aqb zHuKT(2^1v(Ei;U^7Xrkk1R1b>a3IpX>*L~~sFo1c)bx08DKtRH$el)kg^Wm(uF1bD zcJLVD7$Qpfwa>|B_`=+{WPUkmmfSU%Coap18`{2(XVhA>O?qegG_X)FijS!H@^Lei z@dYpsM29NxNiiBW)o+4>2+3@|xx!1Olvn*!5EG{japio2htMfq3w{jGk7$VBk@#RM zlG=|`p4r%fB#nxZDxEc#n+{3%8}vS`{k(w2>r24Boi7@xm~^`hmLI8p%c*yYStl$w zYAC6h*a+&VmtL>3UJs$66Af&UZNT0`fU3UHxg^8JhJ-=1V@^yIu_HEr%Nd4hl`>%6M!2$e9tUdY;18|(Rt!!9maRUNVNcLJ7356^{ z^!M)wiWgw+K9yOfWkClcC;Fe{`f;M2cdrr()0HliV;~5x6-totHQ9=4DI9U#=i2h1 zI2HOJEg$#y#6_@^Y<=78_al{D`+7v-T3L!IuEc^t%g{_LxrI2Ee8?`Rjfy0GGeHs_ zL8+0{C)d*#Lf_P)x%#Boui`M&F-8PxpRJ<(tfrtDM5Y+ zqk|9Jz7^KPd zu9&FFJAG$`I6HnnB3$mio7PUnWWZ7>OM6=^q4 z@N3i1I5zyMvleVFCD!cm^~=D^{dof*?hK5hG6(&@$v)X;*(j=qcWjY-ka^PK!b zRMz0GYUfdZ9a{@lI_pAyyk-BVH-S_)@8-{zClpsr^-c zC<$w(0N`^=Z%c^-%3!Q*?%`#V-loJQPS)(b3!wM5166@B%S{W6*Thnkwsc~JI2Lze zCNbltYA#eg-WHJ=^dp>K$QNzVu%~@ii2TEi6KQ xkr}n#m=oj1xAJvczZ_l37k+EgQ>T!5Fs1g*)t#|nYKo!JyMR_}w!L2#{|~ka1)Kl? literal 0 HcmV?d00001 diff --git a/doc/logos/vulners.png b/doc/logos/vulners.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9bab4c2e8f8855ae31e585895ee07caf77a29d GIT binary patch literal 3942 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 P@4rtR7H?XMagF;wFD66L literal 0 HcmV?d00001 From 8fe002504016ffbfdc5c6c778cac92817f64d8a1 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 21 Nov 2018 11:25:35 +0100 Subject: [PATCH 275/401] chg: Regenerated documentation markdown file --- doc/documentation.md | 686 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 680 insertions(+), 6 deletions(-) diff --git a/doc/documentation.md b/doc/documentation.md index ecb62fa..49fb6b5 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -5,14 +5,34 @@ #### [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). +- **features**: +>The module takes an AS number attribute as input and displays its description and history. +> +>For a proper working, a communication with a redis database is needed, thus 3 parameters are needed: +>- host, the address of the redis server +>- port, the port used by redis +>- db, the index of the database used +> +- **input**: +>Autonomous system number. +- **output**: +>Text containing a description of the ASN and its history. +- **references**: +>https://github.com/CIRCL/ASN-Description-History.git - **requirements**: ->asnhistory +>asnhistory python library ----- #### [btc](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/btc.py) + + An expansion hover module to get a blockchain balance from a BTC address in MISP. +- **input**: +>btc address attribute. +- **output**: +>Text to describe the blockchain balance and the transactions related to the btc address in input. ----- @@ -21,6 +41,18 @@ An expansion hover module to get a blockchain balance from a BTC address in MISP Module to access CIRCL Passive DNS. +- **features**: +>This module takes a hostname, domain or ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive DNS REST API to get and display information about this input. +> +>To make it work a username and a password are thus required to authenticate to the CIRCL Passive DNS API. +- **input**: +>Hostname, domain, or ip-address attribute. +- **ouput**: +>Text describing passive DNS information related to the input attribute. +- **references**: +>https://www.circl.lu/services/passive-dns/, https://datatracker.ietf.org/doc/draft-dulaunoy-dnsop-passive-dns-cof/ +- **requirements**: +>pypdns: Passive DNS python library, A CIRCL passive DNS account with username & password ----- @@ -29,12 +61,32 @@ Module to access CIRCL Passive DNS. Modules to access CIRCL Passive SSL. +- **features**: +>This module takes an ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive SSL REST API to get and display information about this input. +> +>To make it work a username and a password are thus required to authenticate to the CIRCL Passive SSL API. +- **input**: +>Ip-address attribute. +- **output**: +>Text describing passive SSL information related to the input attribute. +- **references**: +>https://www.circl.lu/services/passive-ssl/ +- **requirements**: +>pypssl: Passive SSL python library, A CIRCL passive SSL account with username & password ----- #### [countrycode](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/countrycode.py) Module to expand country codes. +- **features**: +>The module takes a domain or a hostname as input, and returns the country it belongs to. +> +>For non country domains, a list of the most common possible extensions is used. +- **input**: +>Hostname or domain attribute. +- **output**: +>Text with the country code the input belongs to. ----- @@ -43,12 +95,68 @@ Module to expand country codes. Module to query Crowdstrike Falcon. +- **features**: +>This module takes a MISP attribute as input to query a CrowdStrike Falcon API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. +> +>Please note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported. +- **input**: +>A MISP attribute included in the following list: +>- domain +>- email-attachment +>- email-dst +>- email-reply-to +>- email-src +>- email-subject +>- filename +>- hostname +>- ip-src +>- ip-dst +>- md5 +>- mutex +>- regkey +>- sha1 +>- sha256 +>- uri +>- url +>- user-agent +>- whois-registrant-email +>- x509-fingerprint-md5 +- **output**: +>MISP attributes mapped after the CrowdStrike API has been queried, included in the following list: +>- hostname +>- email-src +>- email-subject +>- filename +>- md5 +>- sha1 +>- sha256 +>- ip-dst +>- ip-dst +>- mutex +>- regkey +>- url +>- user-agent +>- x509-fingerprint-md5 +- **references**: +>https://www.crowdstrike.com/products/crowdstrike-falcon-faq/ +- **requirements**: +>A CrowdStrike API access (API id & key) ----- #### [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. +- **features**: +>The module takes a vulnerability attribute as input and queries the CIRCL CVE search API to get information about the vulnerability as it is described in the list of CVEs. +- **input**: +>Vulnerability attribute. +- **output**: +>Text giving information about the CVE related to the Vulnerability. +- **references**: +>https://cve.circl.lu/, https://cve.mitre.org/ ----- @@ -57,12 +165,38 @@ An expansion hover module to expand information about CVE id. Module to check Spamhaus DBL for a domain name. +- **features**: +>This modules takes a domain or a hostname in input and queries the Domain Block List provided by Spamhaus to determine what kind of domain it is. +> +>DBL then returns a response code corresponding to a certain classification of the domain we display. If the queried domain is not in the list, it is also mentionned. +> +>Please note that composite MISP attributes containing domain or hostname are supported as well. +- **input**: +>Domain or hostname attribute. +- **output**: +>Information about the nature of the input. +- **references**: +>https://www.spamhaus.org/faq/section/Spamhaus%20DBL +- **requirements**: +>dnspython3: DNS python3 library ----- #### [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. +A simple DNS expansion service to resolve IP address from domain MISP attributes. +- **features**: +>The module takes a domain of hostname attribute as input, and tries to resolve it. If no error is encountered, the IP address that resolves the domain is returned, otherwise the origin of the error is displayed. +> +>The address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8). +> +>Please note that composite MISP attributes containing domain or hostname are supported as well. +- **input**: +>Domain or hostname attribute. +- **output**: +>IP address resolving the input. +- **requirements**: +>dnspython3: DNS python3 library ----- @@ -71,6 +205,35 @@ A simple DNS expansion service to resolve IP address from MISP attributes. DomainTools MISP expansion module. +- **features**: +>This module takes a MISP attribute as input to query the Domaintools API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. +> +>Please note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported. +- **input**: +>A MISP attribute included in the following list: +>- domain +>- hostname +>- email-src +>- email-dst +>- target-email +>- whois-registrant-email +>- whois-registrant-name +>- whois-registrant-phone +>- ip-src +>- ip-dst +- **output**: +>MISP attributes mapped after the Domaintools API has been queried, included in the following list: +>- whois-registrant-email +>- whois-registrant-phone +>- whois-registrant-name +>- whois-registrar +>- whois-creation-date +>- text +>- domain +- **references**: +>https://www.domaintools.com/ +- **requirements**: +>Domaintools python library, A Domaintools API access (username & apikey) ----- @@ -79,6 +242,18 @@ DomainTools MISP expansion module. A module to query the Phishing Initiative service (https://phishing-initiative.lu). +- **features**: +>This module takes a domain, hostname or url MISP attribute as input to query the Phishing Initiative API. The API returns then the result of the query with some information about the value queried. +> +>Please note that composite attributes containing domain or hostname are also supported. +- **input**: +>A domain, hostname or url MISP attribute. +- **output**: +>Text containing information about the input, resulting from the query on Phishing Initiative. +- **references**: +>https://phishing-initiative.eu/?lang=en +- **requirements**: +>pyeupi: eupi python library, An access to the Phishing Initiative API (apikey & url) ----- @@ -87,30 +262,125 @@ A module to query the Phishing Initiative service (https://phishing-initiative.l Module to access Farsight DNSDB Passive DNS. +- **features**: +>This module takes a domain, hostname or IP address MISP attribute as input to query the Farsight Passive DNS API. The API returns then the result of the query with some information about the value queried. +- **input**: +>A domain, hostname or IP address MISP attribute. +- **output**: +>Text containing information about the input, resulting from the query on the Farsight Passive DNS API. +- **references**: +>https://www.farsightsecurity.com/ +- **requirements**: +>An access to the Farsight Passive DNS API (apikey) ----- #### [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. + + +Module to query a local copy of Maxmind's Geolite database. +- **features**: +>This module takes an IP address MISP attribute as input and queries a local copy of the Maxmind's Geolite database to get information about the location of this IP address. +> +>Please note that composite attributes domain|ip are also supported. +- **input**: +>An IP address MISP Attribute. +- **output**: +>Text containing information about the location of the IP address. +- **references**: +>https://www.maxmind.com/en/home +- **requirements**: +>A local copy of Maxmind's Geolite database + +----- + +#### [hashdd](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/hashdd.py) + +A hover module to check hashes against hashdd.com including NSLR dataset. +- **features**: +>This module takes a hash attribute as input to check its known level, using the hashdd API. This information is then displayed. +- **input**: +>A hash MISP attribute (md5). +- **output**: +>Text describing the known level of the hash in the hashdd databases. +- **references**: +>https://hashdd.com/ ----- #### [intelmq_eventdb](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/intelmq_eventdb.py) + + Module to access intelmqs eventdb. +- **features**: +>/!\ EXPERIMENTAL MODULE, some features may not work /!\ +> +>This module takes a domain, hostname, IP address or Autonomous system MISP attribute as input to query the IntelMQ database. The result of the query gives then additional information about the input. +- **input**: +>A hostname, domain, IP address or AS attribute. +- **output**: +>Text giving information about the input using IntelMQ database. +- **references**: +>https://github.com/certtools/intelmq, https://intelmq.readthedocs.io/en/latest/Developers-Guide/ +- **requirements**: +>psycopg2: Python library to support PostgreSQL, An access to the IntelMQ database (username, password, hostname and database reference) ----- #### [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). +- **features**: +>This module takes an IP address attribute as input and queries the CIRCL IP ASN service to get additional information about the input. +- **input**: +>An IP address MISP attribute. +- **output**: +>Text describing additional information about the input after a query on the IP-ASN-history database. +- **references**: +>https://www.circl.lu/services/ip-asn-history/ +- **requirements**: +>ipasn_redis: Python library to access IP-ASN-history instance via redis, An IP-ASN-history instance information (host, port and database index) ----- #### [iprep](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/iprep.py) Module to query IPRep data for IP addresses. +- **features**: +>This module takes an IP address attribute as input and queries the database from packetmail.net to get some information about the reputation of the IP. +- **input**: +>An IP address MISP attribute. +- **output**: +>Text describing additional information about the input after a query on the IPRep API. +- **references**: +>https://github.com/mahesh557/packetmail +- **requirements**: +>An access to the packetmail API (apikey) + +----- + +#### [macaddress_io](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/macaddress_io.py) + + + +MISP hover module for macaddress.io +- **features**: +>This module takes a MAC address attribute as input and queries macaddress.io for additional information. +> +>This information contains data about: +>- MAC address details +>- Vendor details +>- Block details +- **input**: +>MAC address MISP attribute. +- **output**: +>Text containing information on the MAC address fetched from a query on macaddress.io. +- **references**: +>https://macaddress.io/, https://github.com/CodeLineFi/maclookup-python +- **requirements**: +>maclookup: macaddress.io python library, An access to the macaddress.io API (apikey) ----- @@ -119,6 +389,16 @@ Module to query IPRep data for IP addresses. Module to process a query on Onyphe. +- **features**: +>This module takes a domain, hostname, or IP address attribute as input in order to query the Onyphe API. Data fetched from the query is then parsed and MISP attributes are extracted. +- **input**: +>A domain, hostname or IP address MISP attribute. +- **output**: +>MISP attributes fetched from the Onyphe query. +- **references**: +>https://www.onyphe.io/, https://github.com/sebdraven/pyonyphe +- **requirements**: +>onyphe python library, An access to the Onyphe API (apikey) ----- @@ -127,6 +407,18 @@ Module to process a query on Onyphe. Module to process a full query on Onyphe. +- **features**: +>This module takes a domain, hostname, or IP address attribute as input in order to query the Onyphe API. Data fetched from the query is then parsed and MISP attributes are extracted. +> +>The parsing is here more advanced than the one on onyphe module, and is returning more attributes, since more fields of the query result are watched and parsed. +- **input**: +>A domain, hostname or IP address MISP attribute. +- **output**: +>MISP attributes fetched from the Onyphe query. +- **references**: +>https://www.onyphe.io/, https://github.com/sebdraven/pyonyphe +- **requirements**: +>onyphe python library, An access to the Onyphe API (apikey) ----- @@ -135,6 +427,33 @@ Module to process a full query on Onyphe. Module to get information from AlienVault OTX. +- **features**: +>This module takes a MISP attribute as input to query the OTX Alienvault API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. +- **input**: +>A MISP attribute included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- md5 +>- sha1 +>- sha256 +>- sha512 +- **output**: +>MISP attributes mapped from the result of the query on OTX, included in the following list: +>- domain +>- ip-src +>- ip-dst +>- text +>- md5 +>- sha1 +>- sha256 +>- sha512 +>- email +- **references**: +>https://www.alienvault.com/open-threat-exchange +- **requirements**: +>An access to the OTX API (apikey) ----- @@ -142,21 +461,118 @@ Module to get information from AlienVault OTX. -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 + +- **features**: +>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 +- **input**: +>A MISP attribute included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- x509-fingerprint-sha1 +>- email-src +>- email-dst +>- target-email +>- whois-registrant-email +>- whois-registrant-phone +>- text +>- whois-registrant-name +>- whois-registrar +>- whois-creation-date +- **output**: +>MISP attributes mapped from the result of the query on PassiveTotal, included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- x509-fingerprint-sha1 +>- email-src +>- email-dst +>- target-email +>- whois-registrant-email +>- whois-registrant-phone +>- text +>- whois-registrant-name +>- whois-registrar +>- whois-creation-date +>- md5 +>- sha1 +>- sha256 +>- link +- **references**: +>https://www.passivetotal.org/register +- **requirements**: +>Passivetotal python library, An access to the PassiveTotal API (apikey) ----- #### [rbl](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/rbl.py) Module to check an IPv4 address against known RBLs. +- **features**: +>This module takes an IP address attribute as input and queries multiple know Real-time Blackhost Lists to check if they have already seen this IP address. +> +>We display then all the information we get from those different sources. +- **input**: +>IP address attribute. +- **output**: +>Text with additional data from Real-time Blackhost Lists about the IP address. +- **references**: +>[RBLs list](https://github.com/MISP/misp-modules/blob/8817de476572a10a9c9d03258ec81ca70f3d926d/misp_modules/modules/expansion/rbl.py#L20) - **requirements**: ->dnspython3 +>dnspython3: DNS python3 library ----- #### [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. +- **features**: +>The module takes an IP address as input and tries to find the hostname this IP address is resolved into. +> +>The address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8). +> +>Please note that composite MISP attributes containing IP addresses are supported as well. +- **input**: +>An IP address attribute. +- **output**: +>Hostname attribute the input is resolved into. +- **requirements**: +>DNS python library + +----- + +#### [securitytrails](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/securitytrails.py) + + + +An expansion modules for SecurityTrails. +- **features**: +>The module takes a domain, hostname or IP address attribute as input and queries the SecurityTrails API with it. +> +>Multiple parsing operations are then processed on the result of the query to extract a much information as possible. +> +>From this data extracted are then mapped MISP attributes. +- **input**: +>A domain, hostname or IP address attribute. +- **output**: +>MISP attributes resulting from the query on SecurityTrails API, included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- dns-soa-email +>- whois-registrant-email +>- whois-registrant-phone +>- whois-registrant-name +>- whois-registrar +>- whois-creation-date +>- domain +- **references**: +>https://securitytrails.com/ +- **requirements**: +>dnstrails python library, An access to the SecurityTrails API (apikey) ----- @@ -165,12 +581,90 @@ Simple Reverse DNS expansion service to resolve reverse DNS from MISP attributes Module to query on Shodan. +- **features**: +>The module takes an IP address as input and queries the Shodan API to get some additional data about it. +- **input**: +>An IP address MISP attribute. +- **output**: +>Text with additional data about the input, resulting from the query on Shodan. +- **references**: +>https://www.shodan.io/ +- **requirements**: +>shodan python library, An access to the Shodan API (apikey) + +----- + +#### [sigma_queries](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/sigma_queries.py) + + + +An expansion hover module to display the result of sigma queries. +- **features**: +>This module takes a Sigma rule attribute as input and tries all the different queries available to convert it into different formats recognized by SIEMs. +- **input**: +>A Sigma attribute. +- **output**: +>Text displaying results of queries on the Sigma attribute. +- **references**: +>https://github.com/Neo23x0/sigma/wiki +- **requirements**: +>Sigma python library + +----- + +#### [sigma_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/sigma_syntax_validator.py) + + + +An expansion hover module to perform a syntax check on sigma rules. +- **features**: +>This module takes a Sigma rule attribute as input and performs a syntax check on it. +> +>It displays then that the rule is valid if it is the case, and the error related to the rule otherwise. +- **input**: +>A Sigma attribute. +- **output**: +>Text describing the validity of the Sigma rule. +- **references**: +>https://github.com/Neo23x0/sigma/wiki +- **requirements**: +>Sigma python library, Yaml python library ----- #### [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. +- **features**: +>This module takes a link or url attribute as input and caches the related web page. It returns then a link of the cached page. +- **input**: +>A link or url attribute. +- **output**: +>A malware-sample attribute describing the cached page. +- **references**: +>https://github.com/adulau/url_archiver +- **requirements**: +>urlarchiver: python library to fetch and archive URL on the file-system + +----- + +#### [stix2_pattern_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py) + + + +An expansion hover module to perform a syntax check on stix2 patterns. +- **features**: +>This module takes a STIX2 pattern attribute as input and performs a syntax check on it. +> +>It displays then that the rule is valid if it is the case, and the error related to the rule otherwise. +- **input**: +>A STIX2 pattern attribute. +- **output**: +>Text describing the validity of the STIX2 pattern. +- **references**: +>[STIX2.0 patterning specifications](http://docs.oasis-open.org/cti/stix/v2.0/cs01/part5-stix-patterning/stix-v2.0-cs01-part5-stix-patterning.html) +- **requirements**: +>stix2patterns python library ----- @@ -179,6 +673,35 @@ Module to cache web pages of analysis reports, OSINT sources. The module returns Module to get information from ThreatCrowd. +- **features**: +>This module takes a MISP attribute as input and queries ThreatCrowd with it. +> +>The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. +- **input**: +>A MISP attribute included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- md5 +>- sha1 +>- sha256 +>- sha512 +>- whois-registrant-email +- **output**: +>MISP attributes mapped from the result of the query on ThreatCrowd, included in the following list: +>- domain +>- ip-src +>- ip-dst +>- text +>- md5 +>- sha1 +>- sha256 +>- sha512 +>- hostname +>- whois-registrant-email +- **references**: +>https://www.threatcrowd.org/ ----- @@ -187,6 +710,58 @@ Module to get information from ThreatCrowd. Module to get information from ThreatMiner. +- **features**: +>This module takes a MISP attribute as input and queries ThreatMiner with it. +> +>The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. +- **input**: +>A MISP attribute included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- md5 +>- sha1 +>- sha256 +>- sha512 +- **output**: +>MISP attributes mapped from the result of the query on ThreatMiner, included in the following list: +>- domain +>- ip-src +>- ip-dst +>- text +>- md5 +>- sha1 +>- sha256 +>- sha512 +>- ssdeep +>- authentihash +>- filename +>- whois-registrant-email +>- url +>- link +- **references**: +>https://www.threatminer.org/ + +----- + +#### [urlscan](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/urlscan.py) + + + +An expansion module to query urlscan.io. +- **features**: +>This module takes a MISP attribute as input and queries urlscan.io with it. +> +>The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. +- **input**: +>A domain, hostname or url attribute. +- **output**: +>MISP attributes mapped from the result of the query on urlscan.io. +- **references**: +>https://urlscan.io/ +- **requirements**: +>An access to the urlscan.io API ----- @@ -195,6 +770,22 @@ Module to get information from ThreatMiner. Module to get information from virustotal. +- **features**: +>This module takes a MISP attribute as input and queries the VirusTotal API with it, in order to get additional data on the input attribute. +> +>Multiple recursive requests on the API can then be processed on some attributes found in the first request. A limit can be set to restrict the number of values to query again, and at the same time the number of request submitted to the API. +> +>This limit is important because the default user VirusTotal apikey only allows to process a certain nunmber of queries per minute. As a consequence it is recommended to have a larger number of requests or a private apikey. +> +>Data is then mapped into MISP attributes. +- **input**: +>A domain, hash (md5, sha1, sha256 or sha512), hostname or IP address attribute. +- **output**: +>MISP attributes mapped from the rersult of the query on VirusTotal API. +- **references**: +>https://www.virustotal.com/ +- **requirements**: +>An access to the VirusTotal API (apikey) ----- @@ -203,6 +794,23 @@ Module to get information from virustotal. Module to submit a sample to VMRay. +- **features**: +>This module takes an attachment or malware-sample attribute as input to query the VMRay API. +> +>The sample contained within the attribute in then enriched with data from VMRay mapped into MISP attributes. +- **input**: +>An attachment or malware-sample attribute. +- **output**: +>MISP attributes mapped from the result of the query on VMRay API, included in the following list: +>- text +>- sha1 +>- sha256 +>- md5 +>- link +- **references**: +>https://www.vmray.com/ +- **requirements**: +>An access to the VMRay API (apikey & url) ----- @@ -211,14 +819,54 @@ Module to submit a sample to VMRay. Module to query VulnDB (RiskBasedSecurity.com). +- **features**: +>This module takes a vulnerability attribute as input and queries VulnDB in order to get some additional data about it. +> +>The API gives the result of the query which can be displayed in the screen, and/or mapped into MISP attributes to add in the event. +- **input**: +>A vulnerability attribute. +- **output**: +>Additional data enriching the CVE input, fetched from VulnDB. +- **references**: +>https://vulndb.cyberriskanalytics.com/ +- **requirements**: +>An access to the VulnDB API (apikey, apisecret) + +----- + +#### [vulners](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vulners.py) + + + +An expansion hover module to expand information about CVE id using Vulners API. +- **features**: +>This module takes a vulnerability attribute as input and queries the Vulners API in order to get some additional data about it. +> +>The API then returns details about the vulnerability. +- **input**: +>A vulnerability attribute. +- **output**: +>Text giving additional information about the CVE in input. +- **references**: +>https://vulners.com/ +- **requirements**: +>Vulners python library, An access to the Vulners API ----- #### [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). +- **features**: +>This module takes a domain or IP address attribute as input and queries a 'Univseral Whois proxy server' to get the correct details of the Whois query on the input value (check the references for more details about this whois server). +- **input**: +>A domain or IP address attribute. +- **output**: +>Text describing the result of a whois request for the input value. +- **references**: +>https://github.com/rafiot/uwhoisd - **requirements**: ->uwhois +>uwhois: A whois python library ----- @@ -227,6 +875,16 @@ Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd). An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis. +- **features**: +>This module takes a text attribute as input and queries the Wikidata API. If the text attribute is clear enough to define a specific term, the API returns a wikidata link in response. +- **input**: +>Text attribute. +- **output**: +>Text attribute. +- **references**: +>https://www.wikidata.org +- **requirements**: +>SPARQLWrapper python library ----- @@ -235,6 +893,22 @@ An expansion hover module to extract information from Wikidata to have additiona An expansion module for IBM X-Force Exchange. +- **features**: +>This module takes a MISP attribute as input to query the X-Force API. The API returns then additional information known in their threats data, that is mapped into MISP attributes. +- **input**: +>A MISP attribute included in the following list: +>- ip-src +>- ip-dst +>- vulnerability +>- md5 +>- sha1 +>- sha256 +- **output**: +>MISP attributes mapped from the result of the query on X-Force Exchange. +- **references**: +>https://exchange.xforce.ibmcloud.com/ +- **requirements**: +>An access to the X-Force API (apikey) ----- From 5e7a588d56b0112a80c99a5d1baf2c0820839cc9 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 21 Nov 2018 11:27:01 +0100 Subject: [PATCH 276/401] add: Added missing expansion modules in readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 01ec367..7a93d88 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,11 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [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/). +* [urlscan](misp_modules/modules/expansion/urlscan.py) - an expansion module to query [urlscan.io](https://urlscan.io). * [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/). +* [Vulners](misp_modules/modules/expansion/vulners.py) - an expansion module to expand information about CVEs using Vulners API. * [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. From e30a5d25027a23c054e831a0b168ab5696053f0f Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Wed, 21 Nov 2018 11:31:01 +0100 Subject: [PATCH 277/401] fix: Removed not valid input type --- misp_modules/modules/expansion/urlscan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/urlscan.py b/misp_modules/modules/expansion/urlscan.py index 31c9230..021db92 100644 --- a/misp_modules/modules/expansion/urlscan.py +++ b/misp_modules/modules/expansion/urlscan.py @@ -22,7 +22,7 @@ moduleinfo = { moduleconfig = ['apikey'] misperrors = {'error': 'Error'} mispattributes = { - 'input': ['hostname', 'domain', 'url', 'hash'], + 'input': ['hostname', 'domain', 'url'], 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url', 'text', 'link', 'hash'] } @@ -49,8 +49,6 @@ def handler(q=False): r['results'] += lookup_indicator(client, request['hostname']) if 'url' in request: r['results'] += lookup_indicator(client, request['url']) - if 'hash' in request: - r['results'] += lookup_indicator(client, request['hash']) # Return any errors generated from lookup to the UI and remove duplicates From 96570caeceae6adcb29d4cb288ec8c7a9aaf7630 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Mon, 26 Nov 2018 15:56:11 +0100 Subject: [PATCH 278/401] cosmetic output change --- misp_modules/modules/expansion/btc_steroids.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index adce9b6..e61f775 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -125,7 +125,8 @@ def handler(q=False): mprint("Transactions:\t" + str(n_tx) + "\t (previewing up to 5 most recent)") else: mprint("Transactions:\t" + str(n_tx)) - mprint("======================================================================================") + if n_tx > 0: + mprint("======================================================================================") i = 0 while i < n_tx: if click is False: From d0aec62f1aa8b3ca29a50913c24fbca0bf7b09ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 11 Dec 2018 13:30:52 +0100 Subject: [PATCH 279/401] new: Intel471 module --- REQUIREMENTS | 1 + misp_modules/modules/expansion/__init__.py | 10 +++- misp_modules/modules/expansion/intel471.py | 61 ++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100755 misp_modules/modules/expansion/intel471.py diff --git a/REQUIREMENTS b/REQUIREMENTS index 3bbcc88..29387f1 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -28,3 +28,4 @@ maclookup vulners psutil blockchain +git+https://github.com/MISP/PyIntel471.git diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index daed1ca..0129224 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,3 +1,11 @@ from . import _vmray -__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'btc_steroids', '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', 'yara_query', 'macaddress_io'] +__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', + 'countrycode', 'cve', 'dns', 'btc_steroids', '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', 'yara_query', 'macaddress_io', + 'intel471'] diff --git a/misp_modules/modules/expansion/intel471.py b/misp_modules/modules/expansion/intel471.py new file mode 100755 index 0000000..bf95b2e --- /dev/null +++ b/misp_modules/modules/expansion/intel471.py @@ -0,0 +1,61 @@ +import json +from pyintel471 import PyIntel471 + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['hostname', 'domain', 'url', 'ip-src', 'ip-dst', 'email-src', + 'email-dst', 'target-email', 'whois-registrant-email', + 'whois-registrant-name', 'md5', 'sha1', 'sha256'], 'output': ['freetext']} +moduleinfo = {'version': '0.1', 'author': 'Raphaël Vinot', 'description': 'Module to access Intel 471', + 'module-type': ['hover', 'expansion']} +moduleconfig = ['email', 'authkey'] + + +def cleanup(response): + '''The entries have uids that will be recognised as hashes when they shouldn't''' + j = response.json() + if j['iocTotalCount'] == 0: + return 'Nothing has been found.' + for ioc in j['iocs']: + ioc.pop('uid') + if ioc['links']['actorTotalCount'] > 0: + for actor in ioc['links']['actors']: + actor.pop('uid') + if ioc['links']['reportTotalCount'] > 0: + for report in ioc['links']['reports']: + report.pop('uid') + return json.dumps(j, indent=2) + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + for input_type in mispattributes['input']: + if input_type in request: + to_query = request[input_type] + break + else: + misperrors['error'] = "Unsupported attributes type" + return misperrors + + if (request.get('config')): + if (request['config'].get('email') is None) or (request['config'].get('authkey') is None): + misperrors['error'] = 'Intel 471 authentication is missing' + return misperrors + + intel471 = PyIntel471(email=request['config'].get('email'), authkey=request['config'].get('authkey')) + ioc_filters = intel471.iocs_filters(ioc=to_query) + res = intel471.iocs(filters=ioc_filters) + to_return = cleanup(res) + + r = {'results': [{'types': mispattributes['output'], 'values': to_return}]} + return r + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 8fc5b1fd1f683fb1313aaca8f07cd40be1ebbe02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 11 Dec 2018 15:29:09 +0100 Subject: [PATCH 280/401] fix: Make pep8 happy --- .travis.yml | 3 +- misp_modules/__init__.py | 11 +- misp_modules/helpers/cache.py | 3 +- misp_modules/modules/__init__.py | 6 +- misp_modules/modules/expansion/__init__.py | 2 +- .../expansion/_dnsdb_query/dnsdb_query.py | 65 ++++---- misp_modules/modules/expansion/asn_history.py | 6 +- .../modules/expansion/btc_steroids.py | 57 +++---- misp_modules/modules/expansion/countrycode.py | 40 ++--- .../modules/expansion/dbl_spamhaus.py | 7 +- misp_modules/modules/expansion/dns.py | 2 +- .../modules/expansion/farsight_passivedns.py | 6 +- .../modules/expansion/geoip_country.py | 4 +- misp_modules/modules/expansion/hashdd.py | 2 +- misp_modules/modules/expansion/ipasn.py | 6 +- misp_modules/modules/expansion/iprep.py | 8 +- .../modules/expansion/macaddress_io.py | 46 +++--- misp_modules/modules/expansion/onyphe.py | 12 +- misp_modules/modules/expansion/onyphe_full.py | 28 ++-- misp_modules/modules/expansion/otx.py | 16 +- .../modules/expansion/passivetotal.py | 2 +- misp_modules/modules/expansion/rbl.py | 121 +++++++------- misp_modules/modules/expansion/reversedns.py | 15 +- .../modules/expansion/securitytrails.py | 6 +- .../modules/expansion/sigma_queries.py | 11 +- .../expansion/sigma_syntax_validator.py | 3 + .../stix2_pattern_syntax_validator.py | 5 +- misp_modules/modules/expansion/threatcrowd.py | 20 +-- misp_modules/modules/expansion/threatminer.py | 34 ++-- misp_modules/modules/expansion/urlscan.py | 4 +- misp_modules/modules/expansion/virustotal.py | 4 + .../modules/expansion/vmray_submit.py | 10 +- misp_modules/modules/expansion/vulndb.py | 36 ++-- misp_modules/modules/expansion/vulners.py | 9 +- misp_modules/modules/expansion/wiki.py | 14 +- .../modules/expansion/xforceexchange.py | 136 ++++++++-------- misp_modules/modules/expansion/yara_query.py | 4 + .../expansion/yara_syntax_validator.py | 3 +- misp_modules/modules/export_mod/__init__.py | 3 +- misp_modules/modules/export_mod/cef_export.py | 70 ++++---- .../modules/export_mod/goamlexport.py | 6 +- misp_modules/modules/export_mod/liteexport.py | 129 ++++++++------- .../modules/export_mod/osqueryexport.py | 24 ++- misp_modules/modules/export_mod/pdfexport.py | 2 +- misp_modules/modules/export_mod/testexport.py | 9 +- .../export_mod/threatStream_misp_export.py | 2 - misp_modules/modules/import_mod/__init__.py | 2 +- misp_modules/modules/import_mod/csvimport.py | 34 ++-- .../modules/import_mod/cuckooimport.py | 154 +++++++++--------- .../modules/import_mod/email_import.py | 7 +- .../modules/import_mod/goamlimport.py | 21 ++- misp_modules/modules/import_mod/mispjson.py | 34 ++-- misp_modules/modules/import_mod/ocr.py | 10 +- misp_modules/modules/import_mod/testimport.py | 43 +++-- .../import_mod/threatanalyzer_import.py | 8 +- .../modules/import_mod/vmray_import.py | 8 +- 56 files changed, 695 insertions(+), 638 deletions(-) diff --git a/.travis.yml b/.travis.yml index d7f452e..fd39470 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ python: - "3.7-dev" install: - - pip install -U nose codecov pytest + - pip install -U nose codecov pytest flake8 - pip install -U -r REQUIREMENTS - pip install . @@ -30,6 +30,7 @@ script: - sleep 5 - nosetests --with-coverage --cover-package=misp_modules - kill -s INT $pid + - flake8 --ignore=E501,W503 misp_modules after_success: - coverage combine .coverage* diff --git a/misp_modules/__init__.py b/misp_modules/__init__.py index 7d3c2ce..d933dc9 100644 --- a/misp_modules/__init__.py +++ b/misp_modules/__init__.py @@ -38,14 +38,14 @@ from tornado.concurrent import run_on_executor from concurrent.futures import ThreadPoolExecutor try: - from .modules import * + from .modules import * # noqa HAS_PACKAGE_MODULES = True except Exception as e: print(e) HAS_PACKAGE_MODULES = False try: - from .helpers import * + from .helpers import * # noqa HAS_PACKAGE_HELPERS = True except Exception as e: print(e) @@ -148,7 +148,7 @@ def load_package_modules(): mhandlers = {} modules = [] for path, module in sys.modules.items(): - r = re.findall("misp_modules[.]modules[.](\w+)[.]([^_]\w+)", path) + r = re.findall(r"misp_modules[.]modules[.](\w+)[.]([^_]\w+)", path) if r and len(r[0]) == 2: moduletype, modulename = r[0] mhandlers[modulename] = module @@ -159,6 +159,9 @@ def load_package_modules(): class ListModules(tornado.web.RequestHandler): + global loaded_modules + global mhandlers + def get(self): ret = [] for module in loaded_modules: @@ -238,7 +241,7 @@ def main(): for module in args.m: mispmod = importlib.import_module(module) mispmod.register(mhandlers, loaded_modules) - + service = [(r'/modules', ListModules), (r'/query', QueryModule)] application = tornado.web.Application(service) diff --git a/misp_modules/helpers/cache.py b/misp_modules/helpers/cache.py index 7d70159..77506fa 100644 --- a/misp_modules/helpers/cache.py +++ b/misp_modules/helpers/cache.py @@ -33,7 +33,7 @@ def selftest(enable=True): r = redis.StrictRedis(host=hostname, port=port, db=db) try: r.ping() - except: + except Exception: return 'Redis not running or not installed. Helper will be disabled.' @@ -62,6 +62,7 @@ def flush(): returncode = r.flushdb() return returncode + if __name__ == "__main__": import sys if selftest() is not None: diff --git a/misp_modules/modules/__init__.py b/misp_modules/modules/__init__.py index 65ce6b2..47ddcbf 100644 --- a/misp_modules/modules/__init__.py +++ b/misp_modules/modules/__init__.py @@ -1,3 +1,3 @@ -from .expansion import * -from .import_mod import * -from .export_mod import * +from .expansion import * # noqa +from .import_mod import * # noqa +from .export_mod import * # noqa diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 0129224..18fd78c 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,4 +1,4 @@ -from . import _vmray +from . import _vmray # noqa __all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'btc_steroids', 'domaintools', 'eupi', diff --git a/misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py b/misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py index 0ab58e8..af3f204 100755 --- a/misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py +++ b/misp_modules/modules/expansion/_dnsdb_query/dnsdb_query.py @@ -47,9 +47,11 @@ options = None locale.setlocale(locale.LC_ALL, '') + class QueryError(Exception): pass + class DnsdbClient(object): def __init__(self, server, apikey, limit=None, http_proxy=None, https_proxy=None): self.server = server @@ -81,7 +83,6 @@ class DnsdbClient(object): return self._query(path, before, after) def _query(self, path, before=None, after=None): - res = [] url = '%s/lookup/%s' % (self.server, path) params = {} @@ -120,12 +121,15 @@ class DnsdbClient(object): except (HTTPError, URLError) as e: raise QueryError(str(e), sys.exc_traceback) + def quote(path): return urllib_quote(path, safe='') + def sec_to_text(ts): return time.strftime('%Y-%m-%d %H:%M:%S -0000', time.gmtime(ts)) + def rrset_to_text(m): s = StringIO() @@ -155,9 +159,11 @@ def rrset_to_text(m): finally: s.close() + def rdata_to_text(m): return '%s IN %s %s' % (m['rrname'], m['rrtype'], m['rdata']) + def parse_config(cfg_files): config = {} @@ -172,6 +178,7 @@ def parse_config(cfg_files): return config + def time_parse(s): try: epoch = int(s) @@ -193,14 +200,15 @@ def time_parse(s): m = re.match(r'^(?=\d)(?:(\d+)w)?(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$', s, re.I) if m: - return -1*(int(m.group(1) or 0)*604800 + - int(m.group(2) or 0)*86400+ - int(m.group(3) or 0)*3600+ - int(m.group(4) or 0)*60+ - int(m.group(5) or 0)) + return -1 * (int(m.group(1) or 0) * 604800 + + int(m.group(2) or 0) * 86400 + + int(m.group(3) or 0) * 3600 + + int(m.group(4) or 0) * 60 + + int(m.group(5) or 0)) raise ValueError('Invalid time: "%s"' % s) + def epipe_wrapper(func): def f(*args, **kwargs): try: @@ -211,31 +219,23 @@ def epipe_wrapper(func): raise return f + @epipe_wrapper def main(): global cfg global options parser = optparse.OptionParser(epilog='Time formats are: "%Y-%m-%d", "%Y-%m-%d %H:%M:%S", "%d" (UNIX timestamp), "-%d" (Relative time in seconds), BIND format (e.g. 1w1h, (w)eek, (d)ay, (h)our, (m)inute, (s)econd)') - parser.add_option('-c', '--config', dest='config', - help='config file', action='append') - parser.add_option('-r', '--rrset', dest='rrset', type='string', - help='rrset [/[/BAILIWICK]]') - parser.add_option('-n', '--rdataname', dest='rdata_name', type='string', - help='rdata name [/]') - parser.add_option('-i', '--rdataip', dest='rdata_ip', type='string', - help='rdata ip ') - parser.add_option('-t', '--rrtype', dest='rrtype', type='string', - help='rrset or rdata rrtype') - parser.add_option('-b', '--bailiwick', dest='bailiwick', type='string', - help='rrset bailiwick') + parser.add_option('-c', '--config', dest='config', help='config file', action='append') + parser.add_option('-r', '--rrset', dest='rrset', type='string', help='rrset [/[/BAILIWICK]]') + parser.add_option('-n', '--rdataname', dest='rdata_name', type='string', help='rdata name [/]') + parser.add_option('-i', '--rdataip', dest='rdata_ip', type='string', help='rdata ip ') + parser.add_option('-t', '--rrtype', dest='rrtype', type='string', help='rrset or rdata rrtype') + parser.add_option('-b', '--bailiwick', dest='bailiwick', type='string', help='rrset bailiwick') parser.add_option('-s', '--sort', dest='sort', type='string', help='sort key') - parser.add_option('-R', '--reverse', dest='reverse', action='store_true', default=False, - help='reverse sort') - parser.add_option('-j', '--json', dest='json', action='store_true', default=False, - help='output in JSON format') - parser.add_option('-l', '--limit', dest='limit', type='int', default=0, - help='limit number of results') + parser.add_option('-R', '--reverse', dest='reverse', action='store_true', default=False, help='reverse sort') + parser.add_option('-j', '--json', dest='json', action='store_true', default=False, help='output in JSON format') + parser.add_option('-l', '--limit', dest='limit', type='int', default=0, help='limit number of results') parser.add_option('', '--before', dest='before', type='string', help='only output results seen before this time') parser.add_option('', '--after', dest='after', type='string', help='only output results seen after this time') @@ -263,20 +263,20 @@ def main(): print(str(e), file=sys.stderr) sys.exit(1) - if not 'DNSDB_SERVER' in cfg: + if 'DNSDB_SERVER' not in cfg: cfg['DNSDB_SERVER'] = DEFAULT_DNSDB_SERVER - if not 'HTTP_PROXY' in cfg: + if 'HTTP_PROXY' not in cfg: cfg['HTTP_PROXY'] = DEFAULT_HTTP_PROXY - if not 'HTTPS_PROXY' in cfg: + if 'HTTPS_PROXY' not in cfg: cfg['HTTPS_PROXY'] = DEFAULT_HTTPS_PROXY - if not 'APIKEY' in cfg: + if 'APIKEY' not in cfg: sys.stderr.write('dnsdb_query: APIKEY not defined in config file\n') sys.exit(1) client = DnsdbClient(cfg['DNSDB_SERVER'], cfg['APIKEY'], - limit=options.limit, - http_proxy=cfg['HTTP_PROXY'], - https_proxy=cfg['HTTPS_PROXY']) + limit=options.limit, + http_proxy=cfg['HTTP_PROXY'], + https_proxy=cfg['HTTPS_PROXY']) if options.rrset: if options.rrtype or options.bailiwick: qargs = (options.rrset, options.rrtype, options.bailiwick) @@ -307,7 +307,7 @@ def main(): if options.sort: results = list(results) if len(results) > 0: - if not options.sort in results[0]: + if options.sort not in results[0]: sort_keys = results[0].keys() sort_keys.sort() sys.stderr.write('dnsdb_query: invalid sort key "%s". valid sort keys are %s\n' % (options.sort, ', '.join(sort_keys))) @@ -319,5 +319,6 @@ def main(): print(e.message, file=sys.stderr) sys.exit(1) + if __name__ == '__main__': main() diff --git a/misp_modules/modules/expansion/asn_history.py b/misp_modules/modules/expansion/asn_history.py index 9775f6d..5a2f53d 100755 --- a/misp_modules/modules/expansion/asn_history.py +++ b/misp_modules/modules/expansion/asn_history.py @@ -22,9 +22,9 @@ def handler(q=False): misperrors['error'] = "Unsupported attributes type" return misperrors - if not request.get('config') and not (request['config'].get('host') and - request['config'].get('port') and - request['config'].get('db')): + if not request.get('config') and not (request['config'].get('host') + and request['config'].get('port') + and request['config'].get('db')): misperrors['error'] = 'ASN description history configuration is missing' return misperrors diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index e61f775..725cdf9 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -12,24 +12,25 @@ moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', moduleconfig = [] -blockchain_firstseen='https://blockchain.info/q/addressfirstseen/' -blockchain_balance='https://blockchain.info/q/addressbalance/' -blockchain_totalreceived='https://blockchain.info/q/getreceivedbyaddress/' -blockchain_all='https://blockchain.info/rawaddr/' -converter = 'https://min-api.cryptocompare.com/data/pricehistorical?fsym=BTC&tsyms=USD,EUR&ts=' +blockchain_firstseen = 'https://blockchain.info/q/addressfirstseen/' +blockchain_balance = 'https://blockchain.info/q/addressbalance/' +blockchain_totalreceived = 'https://blockchain.info/q/getreceivedbyaddress/' +blockchain_all = 'https://blockchain.info/rawaddr/{}?filter=5{}' +converter = 'https://min-api.cryptocompare.com/data/pricehistorical?fsym=BTC&tsyms=USD,EUR&ts={}' converter_rls = 'https://min-api.cryptocompare.com/stats/rate/limit' result_text = "" g_rate_limit = 300 start_time = 0 conversion_rates = {} + def get_consumption(output=False): try: req = requests.get(converter_rls) jreq = req.json() minute = str(jreq['Data']['calls_left']['minute']) - hour = str(jreq['Data']['calls_left']['hour']) - except: + hour = str(jreq['Data']['calls_left']['hour']) + except Exception: minute = str(-1) hour = str(-1) # Debug out for the console @@ -53,20 +54,20 @@ def convert(btc, timestamp): minute, hour = get_consumption() g_rate_limit -= 1 now = time.time() - delta = now - start_time - #print(g_rate_limit) + # delta = now - start_time + # print(g_rate_limit) if g_rate_limit <= 10: minute, hour = get_consumption(output=True) if int(minute) <= 10: - #print(minute) - #get_consumption(output=True) + # print(minute) + # get_consumption(output=True) time.sleep(3) else: mprint(minute) start_time = time.time() g_rate_limit = int(minute) try: - req = requests.get(converter+str(timestamp)) + req = requests.get(converter.format(timestamp)) jreq = req.json() usd = jreq['BTC']['USD'] eur = jreq['BTC']['EUR'] @@ -78,7 +79,7 @@ def convert(btc, timestamp): # Actually convert and return the values u = usd * btc e = eur * btc - return u,e + return u, e def mprint(input): @@ -90,8 +91,8 @@ def mprint(input): def handler(q=False): global result_text global conversion_rates - start_time = time.time() - now = time.time() + # start_time = time.time() + # now = time.time() if q is False: return False request = json.loads(q) @@ -107,13 +108,13 @@ def handler(q=False): mprint("\nAddress:\t" + btc) try: - req = requests.get(blockchain_all+btc+"?limit=50&filter=5") + req = requests.get(blockchain_all.format(btc, "&limit=50")) jreq = req.json() - except Exception as e: - #print(e) + except Exception: + # print(e) print(req.text) result_text = "" - sys.exit(1) + sys.exit(1) n_tx = jreq['n_tx'] balance = float(jreq['final_balance'] / 100000000) @@ -130,11 +131,11 @@ def handler(q=False): i = 0 while i < n_tx: if click is False: - req = requests.get(blockchain_all+btc+"?limit=5&offset="+str(i)+"&filter=5") + req = requests.get(blockchain_all.format(btc, "&limit=5&offset={}".format(i))) if n_tx > 5: n_tx = 5 else: - req = requests.get(blockchain_all+btc+"?limit=50&offset="+str(i)+"&filter=5") + req = requests.get(blockchain_all.format(btc, "&limit=50&offset={}".format(i))) jreq = req.json() if jreq['txs']: for transactions in jreq['txs']: @@ -144,8 +145,8 @@ def handler(q=False): script_old = tx['script'] if tx['prev_out']['value'] != 0 and tx['prev_out']['addr'] == btc: datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time']))) - value = float(tx['prev_out']['value'] / 100000000 ) - u,e = convert(value, transactions['time']) + value = float(tx['prev_out']['value'] / 100000000) + u, e = convert(value, transactions['time']) mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0')) if script_old != tx['script']: i += 1 @@ -153,19 +154,19 @@ def handler(q=False): sum_counter += 1 sum += value if sum_counter > 1: - u,e = convert(sum, transactions['time']) + u, e = convert(sum, transactions['time']) mprint("\t\t\t\t\t----------------------------------------------") mprint("#" + str(n_tx - i) + "\t\t\t\t Sum:\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR\n".format(sum, u, e).rstrip('0')) for tx in transactions['out']: if tx['value'] != 0 and tx['addr'] == btc: datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time']))) - value = float(tx['value'] / 100000000 ) - u,e = convert(value, transactions['time']) + value = float(tx['value'] / 100000000) + u, e = convert(value, transactions['time']) mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t {0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0')) - #i += 1 + # i += 1 i += 1 - r = { + r = { 'results': [ { 'types': ['text'], diff --git a/misp_modules/modules/expansion/countrycode.py b/misp_modules/modules/expansion/countrycode.py index fb38fe4..64c0950 100755 --- a/misp_modules/modules/expansion/countrycode.py +++ b/misp_modules/modules/expansion/countrycode.py @@ -12,20 +12,21 @@ moduleinfo = {'version': '1', 'author': 'Hannah Ward', # config fields that your code expects from the site admin moduleconfig = [] -common_tlds = {"com":"Commercial (Worldwide)", - "org":"Organisation (Worldwide)", - "net":"Network (Worldwide)", - "int":"International (Worldwide)", - "edu":"Education (Usually USA)", - "gov":"Government (USA)" - } +common_tlds = {"com": "Commercial (Worldwide)", + "org": "Organisation (Worldwide)", + "net": "Network (Worldwide)", + "int": "International (Worldwide)", + "edu": "Education (Usually USA)", + "gov": "Government (USA)" + } codes = False + def handler(q=False): global codes if not codes: - codes = requests.get("http://www.geognos.com/api/en/countries/info/all.json").json() + codes = requests.get("http://www.geognos.com/api/en/countries/info/all.json").json() if q is False: return False request = json.loads(q) @@ -36,18 +37,18 @@ def handler(q=False): # Check if it's a common, non country one if ext in common_tlds.keys(): - val = common_tlds[ext] + val = common_tlds[ext] else: - # Retrieve a json full of country info - if not codes["StatusMsg"] == "OK": - val = "Unknown" - else: - # Find our code based on TLD - codes = codes["Results"] - for code in codes.keys(): - if codes[code]["CountryCodes"]["tld"] == ext: - val = codes[code]["Name"] - r = {'results': [{'types':['text'], 'values':[val]}]} + # Retrieve a json full of country info + if not codes["StatusMsg"] == "OK": + val = "Unknown" + else: + # Find our code based on TLD + codes = codes["Results"] + for code in codes.keys(): + if codes[code]["CountryCodes"]["tld"] == ext: + val = codes[code]["Name"] + r = {'results': [{'types': ['text'], 'values':[val]}]} return r @@ -58,4 +59,3 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig return moduleinfo - diff --git a/misp_modules/modules/expansion/dbl_spamhaus.py b/misp_modules/modules/expansion/dbl_spamhaus.py index f372961..529815b 100644 --- a/misp_modules/modules/expansion/dbl_spamhaus.py +++ b/misp_modules/modules/expansion/dbl_spamhaus.py @@ -1,6 +1,5 @@ import json -import datetime -from collections import defaultdict +import sys try: import dns.resolver @@ -30,12 +29,14 @@ dbl_mapping = {'127.0.1.2': 'spam domain', '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 @@ -52,9 +53,11 @@ def handler(q=False): result = str(e) return {'results': [{'types': mispattributes.get('output'), 'values': result}]} + def introspection(): return mispattributes + def version(): moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/expansion/dns.py b/misp_modules/modules/expansion/dns.py index 41da8b2..c5af9d6 100755 --- a/misp_modules/modules/expansion/dns.py +++ b/misp_modules/modules/expansion/dns.py @@ -43,7 +43,7 @@ def handler(q=False): except dns.exception.Timeout: misperrors['error'] = "Timeout" return misperrors - except: + except Exception: misperrors['error'] = "DNS resolving error" return misperrors diff --git a/misp_modules/modules/expansion/farsight_passivedns.py b/misp_modules/modules/expansion/farsight_passivedns.py index 2518771..7c1aa27 100755 --- a/misp_modules/modules/expansion/farsight_passivedns.py +++ b/misp_modules/modules/expansion/farsight_passivedns.py @@ -51,7 +51,7 @@ def lookup_name(client, name): for i in item.get('rdata'): # grab email field and replace first dot by @ to convert to an email address yield(i.split(' ')[1].rstrip('.').replace('.', '@', 1)) - except QueryError as e: + except QueryError: pass try: @@ -59,7 +59,7 @@ def lookup_name(client, name): for item in res: if item.get('rrtype') in ['A', 'AAAA', 'CNAME']: yield(item.get('rrname').rstrip('.')) - except QueryError as e: + except QueryError: pass @@ -68,7 +68,7 @@ def lookup_ip(client, ip): res = client.query_rdata_ip(ip) for item in res: yield(item['rrname'].rstrip('.')) - except QueryError as e: + except QueryError: pass diff --git a/misp_modules/modules/expansion/geoip_country.py b/misp_modules/modules/expansion/geoip_country.py index 31c1b6a..1709d91 100644 --- a/misp_modules/modules/expansion/geoip_country.py +++ b/misp_modules/modules/expansion/geoip_country.py @@ -27,7 +27,7 @@ try: config.read(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'geoip_country.cfg')) gi = pygeoip.GeoIP(config.get('GEOIP', 'database')) enabled = True -except: +except Exception: enabled = False @@ -49,7 +49,7 @@ def handler(q=False): try: answer = gi.country_code_by_addr(toquery) - except: + except Exception: misperrors['error'] = "GeoIP resolving error" return misperrors diff --git a/misp_modules/modules/expansion/hashdd.py b/misp_modules/modules/expansion/hashdd.py index 2ab346b..907447f 100755 --- a/misp_modules/modules/expansion/hashdd.py +++ b/misp_modules/modules/expansion/hashdd.py @@ -20,7 +20,7 @@ def handler(q=False): if v is None: misperrors['error'] = 'Hash value is missing.' return misperrors - r = requests.post(hashddapi_url, data={'hash':v}) + r = requests.post(hashddapi_url, data={'hash': v}) if r.status_code == 200: state = json.loads(r.text) if state: diff --git a/misp_modules/modules/expansion/ipasn.py b/misp_modules/modules/expansion/ipasn.py index 22c9a70..f47d780 100755 --- a/misp_modules/modules/expansion/ipasn.py +++ b/misp_modules/modules/expansion/ipasn.py @@ -24,9 +24,9 @@ def handler(q=False): misperrors['error'] = "Unsupported attributes type" return misperrors - if not request.get('config') and not (request['config'].get('host') and - request['config'].get('port') and - request['config'].get('db')): + if not request.get('config') and not (request['config'].get('host') + and request['config'].get('port') + and request['config'].get('db')): misperrors['error'] = 'IP ASN history configuration is missing' return misperrors diff --git a/misp_modules/modules/expansion/iprep.py b/misp_modules/modules/expansion/iprep.py index 6073052..558dbdd 100755 --- a/misp_modules/modules/expansion/iprep.py +++ b/misp_modules/modules/expansion/iprep.py @@ -45,7 +45,7 @@ def parse_iprep(ip, api): url = 'https://www.packetmail.net/iprep.php/%s' % ip try: data = requests.get(url, params={'apikey': api}).json() - except: + except Exception: return ['Error pulling data'], rep # print '%s' % data for name, val in data.items(): @@ -71,11 +71,11 @@ def parse_iprep(ip, api): misp_val = context full_text += '\n%s' % context misp_comment = 'IPRep Source %s: %s' % (name, val['last_seen']) - rep.append({'types': mispattributes['output'], 'categories':['External analysis'], 'values': misp_val, 'comment': misp_comment}) - except: + rep.append({'types': mispattributes['output'], 'categories': ['External analysis'], 'values': misp_val, 'comment': misp_comment}) + except Exception: err.append('Error parsing source: %s' % name) - rep.append({'types': ['freetext'], 'values': full_text , 'comment': 'Free text import of IPRep'}) + rep.append({'types': ['freetext'], 'values': full_text, 'comment': 'Free text import of IPRep'}) return err, rep diff --git a/misp_modules/modules/expansion/macaddress_io.py b/misp_modules/modules/expansion/macaddress_io.py index e735f39..e72fa1e 100644 --- a/misp_modules/modules/expansion/macaddress_io.py +++ b/misp_modules/modules/expansion/macaddress_io.py @@ -86,32 +86,30 @@ def handler(q=False): 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, + '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, + # 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': date_created, - 'Date when the range was last updated': date_updated - } - } - ] + # 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': date_created, + 'Date when the range was last updated': date_updated + }}] } return results diff --git a/misp_modules/modules/expansion/onyphe.py b/misp_modules/modules/expansion/onyphe.py index e9c496a..aceaf05 100644 --- a/misp_modules/modules/expansion/onyphe.py +++ b/misp_modules/modules/expansion/onyphe.py @@ -1,4 +1,3 @@ -import json # -*- coding: utf-8 -*- import json @@ -9,7 +8,8 @@ except ImportError: misperrors = {'error': 'Error'} -mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domain'], 'output': ['hostname', 'domain', 'ip-src', 'ip-dst','url']} +mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domain'], + 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url']} # possible module-types: 'expansion', 'hover' or both moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', 'description': 'Query on Onyphe', @@ -54,7 +54,7 @@ def handle_expansion(api, ip, misperrors): misperrors['error'] = result['message'] return misperrors - categories = list(set([item['@category'] for item in result['results']])) + # categories = list(set([item['@category'] for item in result['results']])) result_filtered = {"results": []} urls_pasties = [] @@ -72,9 +72,9 @@ def handle_expansion(api, ip, misperrors): os_target = r['os'] if os_target != 'Unknown': os_list.append(r['os']) - elif r['@category'] == 'resolver' and r['type'] =='reverse': + elif r['@category'] == 'resolver' and r['type'] == 'reverse': domains_resolver.append(r['reverse']) - elif r['@category'] == 'resolver' and r['type'] =='forward': + elif r['@category'] == 'resolver' and r['type'] == 'forward': domains_forward.append(r['forward']) result_filtered['results'].append({'types': ['url'], 'values': urls_pasties, @@ -90,7 +90,7 @@ def handle_expansion(api, ip, misperrors): result_filtered['results'].append({'types': ['domain'], 'values': list(set(domains_resolver)), 'categories': ['Network activity'], - 'comment': 'resolver to %s' % ip }) + 'comment': 'resolver to %s' % ip}) result_filtered['results'].append({'types': ['domain'], 'values': list(set(domains_forward)), diff --git a/misp_modules/modules/expansion/onyphe_full.py b/misp_modules/modules/expansion/onyphe_full.py index 3d6ef8e..aadb744 100644 --- a/misp_modules/modules/expansion/onyphe_full.py +++ b/misp_modules/modules/expansion/onyphe_full.py @@ -1,4 +1,3 @@ -import json # -*- coding: utf-8 -*- import json @@ -10,7 +9,7 @@ except ImportError: misperrors = {'error': 'Error'} mispattributes = {'input': ['ip-src', 'ip-dst', 'hostname', 'domain'], - 'output': ['hostname', 'domain', 'ip-src', 'ip-dst','url']} + 'output': ['hostname', 'domain', 'ip-src', 'ip-dst', 'url']} # possible module-types: 'expansion', 'hover' or both moduleinfo = {'version': '1', 'author': 'Sebastien Larinier @sebdraven', @@ -38,10 +37,10 @@ def handler(q=False): ip = '' if request.get('ip-src'): ip = request['ip-src'] - return handle_ip(api ,ip, misperrors) + return handle_ip(api, ip, misperrors) elif request.get('ip-dst'): ip = request['ip-dst'] - return handle_ip(api,ip,misperrors) + return handle_ip(api, ip, misperrors) elif request.get('domain'): domain = request['domain'] return handle_domain(api, domain, misperrors) @@ -91,11 +90,11 @@ def handle_ip(api, ip, misperrors): r, status_ok = expand_syscan(api, ip, misperrors) if status_ok: - result_filtered['results'].extend(r) + result_filtered['results'].extend(r) else: - misperrors['error'] = "Error syscan result" + misperrors['error'] = "Error syscan result" - r, status_ok = expand_pastries(api,misperrors,ip=ip) + r, status_ok = expand_pastries(api, misperrors, ip=ip) if status_ok: result_filtered['results'].extend(r) @@ -185,11 +184,11 @@ def expand_syscan(api, ip, misperror): return r, status_ok -def expand_datascan(api, misperror,**kwargs): +def expand_datascan(api, misperror, **kwargs): status_ok = False r = [] - ip = '' - query ='' + # ip = '' + query = '' asn_list = [] geoloc = [] orgs = [] @@ -311,7 +310,7 @@ def expand_pastries(api, misperror, **kwargs): query = kwargs.get('domain') result = api.search_pastries('domain:%s' % query) - if result['status'] =='ok': + if result['status'] == 'ok': status_ok = True for item in result['results']: if item['@category'] == 'pastries': @@ -328,7 +327,7 @@ def expand_pastries(api, misperror, **kwargs): r.append({'types': ['url'], 'values': urls_pasties, 'categories': ['External analysis'], - 'comment':'URLs of pasties where %s has found' % query}) + 'comment': 'URLs of pasties where %s has found' % query}) r.append({'types': ['domain'], 'values': list(set(domains)), 'categories': ['Network activity'], 'comment': 'Domains found in pasties of Onyphe'}) @@ -340,7 +339,7 @@ def expand_pastries(api, misperror, **kwargs): return r, status_ok -def expand_threatlist(api, misperror,**kwargs): +def expand_threatlist(api, misperror, **kwargs): status_ok = False r = [] @@ -366,7 +365,8 @@ def expand_threatlist(api, misperror,**kwargs): 'comment': '%s is present in threatlist' % query }) - return r,status_ok + return r, status_ok + def introspection(): return mispattributes diff --git a/misp_modules/modules/expansion/otx.py b/misp_modules/modules/expansion/otx.py index 86685eb..e586180 100755 --- a/misp_modules/modules/expansion/otx.py +++ b/misp_modules/modules/expansion/otx.py @@ -15,9 +15,10 @@ moduleinfo = {'version': '1', 'author': 'chrisdoman', # We're not actually using the API key yet moduleconfig = ["apikey"] + # Avoid adding windows update to enrichment etc. def isBlacklisted(value): - blacklist = ['0.0.0.0', '8.8.8.8', '255.255.255.255', '192.168.56.' , 'time.windows.com'] + blacklist = ['0.0.0.0', '8.8.8.8', '255.255.255.255', '192.168.56.', 'time.windows.com'] for b in blacklist: if value in b: @@ -25,10 +26,12 @@ def isBlacklisted(value): return True + def valid_ip(ip): m = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", ip) return bool(m) and all(map(lambda n: 0 <= int(n) <= 255, m.groups())) + def findAll(data, keys): a = [] if isinstance(data, dict): @@ -43,9 +46,11 @@ def findAll(data, keys): a.extend(findAll(i, keys)) return a + def valid_email(email): return bool(re.search(r"[a-zA-Z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?", email)) + def handler(q=False): if q is False: return False @@ -99,19 +104,17 @@ def getHash(_hash, key): def getIP(ip, key): ret = [] - req = json.loads( requests.get("https://otx.alienvault.com/otxapi/indicator/ip/malware/" + ip + "?limit=1000").text ) + 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]}) - - req = json.loads( requests.get("https://otx.alienvault.com/otxapi/indicator/ip/passive_dns/" + ip).text ) + req = json.loads(requests.get("https://otx.alienvault.com/otxapi/indicator/ip/passive_dns/" + ip).text) for hostname in findAll(req, "hostname"): if not isBlacklisted(hostname): ret.append({"types": ["hostname"], "values": [hostname]}) - return ret @@ -119,7 +122,7 @@ def getDomain(domain, key): ret = [] - req = json.loads( requests.get("https://otx.alienvault.com/otxapi/indicator/domain/malware/" + domain + "?limit=1000").text ) + 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]}) @@ -144,6 +147,7 @@ def getDomain(domain, key): return ret + def introspection(): return mispattributes diff --git a/misp_modules/modules/expansion/passivetotal.py b/misp_modules/modules/expansion/passivetotal.py index 5325d19..6bf2f93 100755 --- a/misp_modules/modules/expansion/passivetotal.py +++ b/misp_modules/modules/expansion/passivetotal.py @@ -331,7 +331,7 @@ def handler(q=False): output['results'] += results else: log.error("Unsupported query pattern issued.") - except: + except Exception: return misperrors return output diff --git a/misp_modules/modules/expansion/rbl.py b/misp_modules/modules/expansion/rbl.py index bcdcec3..b9b89bb 100644 --- a/misp_modules/modules/expansion/rbl.py +++ b/misp_modules/modules/expansion/rbl.py @@ -1,5 +1,5 @@ import json -import datetime +import sys try: import dns.resolver @@ -18,64 +18,65 @@ moduleinfo = {'version': '0.1', 'author': 'Christian Studer', moduleconfig = [] rbls = { - 'spam.spamrats.com': 'http://www.spamrats.com', - 'spamguard.leadmon.net': 'http://www.leadmon.net/SpamGuard/', - 'rbl-plus.mail-abuse.org': 'http://www.mail-abuse.com/lookup.html', - 'web.dnsbl.sorbs.net': 'http://www.sorbs.net', - 'ix.dnsbl.manitu.net': 'http://www.dnsbl.manitu.net', - 'virus.rbl.jp': 'http://www.rbl.jp', - 'dul.dnsbl.sorbs.net': 'http://www.sorbs.net', - 'bogons.cymru.com': 'http://www.team-cymru.org/Services/Bogons/', - 'psbl.surriel.com': 'http://psbl.surriel.com', - 'misc.dnsbl.sorbs.net': 'http://www.sorbs.net', - 'httpbl.abuse.ch': 'http://dnsbl.abuse.ch', - 'combined.njabl.org': 'http://combined.njabl.org', - 'smtp.dnsbl.sorbs.net': 'http://www.sorbs.net', - 'korea.services.net': 'http://korea.services.net', - 'drone.abuse.ch': 'http://dnsbl.abuse.ch', - 'rbl.efnetrbl.org': 'http://rbl.efnetrbl.org', - 'cbl.anti-spam.org.cn': 'http://www.anti-spam.org.cn/?Locale=en_US', - 'b.barracudacentral.org': 'http://www.barracudacentral.org/rbl/removal-request', - 'bl.spamcannibal.org': 'http://www.spamcannibal.org', - 'xbl.spamhaus.org': 'http://www.spamhaus.org/xbl/', - 'zen.spamhaus.org': 'http://www.spamhaus.org/zen/', - 'rbl.suresupport.com': 'http://suresupport.com/postmaster', - 'db.wpbl.info': 'http://www.wpbl.info', - 'sbl.spamhaus.org': 'http://www.spamhaus.org/sbl/', - 'http.dnsbl.sorbs.net': 'http://www.sorbs.net', - 'csi.cloudmark.com': 'http://www.cloudmark.com/en/products/cloudmark-sender-intelligence/index', - 'rbl.interserver.net': 'http://rbl.interserver.net', - 'ubl.unsubscore.com': 'http://www.lashback.com/blacklist/', - 'dnsbl.sorbs.net': 'http://www.sorbs.net', - 'virbl.bit.nl': 'http://virbl.bit.nl', - 'pbl.spamhaus.org': 'http://www.spamhaus.org/pbl/', - 'socks.dnsbl.sorbs.net': 'http://www.sorbs.net', - 'short.rbl.jp': 'http://www.rbl.jp', - 'dnsbl.dronebl.org': 'http://www.dronebl.org', - 'blackholes.mail-abuse.org': 'http://www.mail-abuse.com/lookup.html', - 'truncate.gbudb.net': 'http://www.gbudb.com/truncate/index.jsp', - 'dyna.spamrats.com': 'http://www.spamrats.com', - 'spamrbl.imp.ch': 'http://antispam.imp.ch', - 'spam.dnsbl.sorbs.net': 'http://www.sorbs.net', - 'wormrbl.imp.ch': 'http://antispam.imp.ch', - 'query.senderbase.org': 'http://www.senderbase.org/about', - 'opm.tornevall.org': 'http://dnsbl.tornevall.org', - 'netblock.pedantic.org': 'http://pedantic.org', - 'access.redhawk.org': 'http://www.redhawk.org/index.php?option=com_wrapper&Itemid=33', - 'cdl.anti-spam.org.cn': 'http://www.anti-spam.org.cn/?Locale=en_US', - 'multi.surbl.org': 'http://www.surbl.org', - 'noptr.spamrats.com': 'http://www.spamrats.com', - 'dnsbl.inps.de': 'http://dnsbl.inps.de/index.cgi?lang=en', - 'bl.spamcop.net': 'http://bl.spamcop.net', - 'cbl.abuseat.org': 'http://cbl.abuseat.org', - 'dsn.rfc-ignorant.org': 'http://www.rfc-ignorant.org/policy-dsn.php', - 'zombie.dnsbl.sorbs.net': 'http://www.sorbs.net', - 'dnsbl.njabl.org': 'http://dnsbl.njabl.org', - 'relays.mail-abuse.org': 'http://www.mail-abuse.com/lookup.html', - 'rbl.spamlab.com': 'http://tools.appriver.com/index.aspx?tool=rbl', - 'all.bl.blocklist.de': 'http://www.blocklist.de/en/rbldns.html' + 'spam.spamrats.com': 'http://www.spamrats.com', + 'spamguard.leadmon.net': 'http://www.leadmon.net/SpamGuard/', + 'rbl-plus.mail-abuse.org': 'http://www.mail-abuse.com/lookup.html', + 'web.dnsbl.sorbs.net': 'http://www.sorbs.net', + 'ix.dnsbl.manitu.net': 'http://www.dnsbl.manitu.net', + 'virus.rbl.jp': 'http://www.rbl.jp', + 'dul.dnsbl.sorbs.net': 'http://www.sorbs.net', + 'bogons.cymru.com': 'http://www.team-cymru.org/Services/Bogons/', + 'psbl.surriel.com': 'http://psbl.surriel.com', + 'misc.dnsbl.sorbs.net': 'http://www.sorbs.net', + 'httpbl.abuse.ch': 'http://dnsbl.abuse.ch', + 'combined.njabl.org': 'http://combined.njabl.org', + 'smtp.dnsbl.sorbs.net': 'http://www.sorbs.net', + 'korea.services.net': 'http://korea.services.net', + 'drone.abuse.ch': 'http://dnsbl.abuse.ch', + 'rbl.efnetrbl.org': 'http://rbl.efnetrbl.org', + 'cbl.anti-spam.org.cn': 'http://www.anti-spam.org.cn/?Locale=en_US', + 'b.barracudacentral.org': 'http://www.barracudacentral.org/rbl/removal-request', + 'bl.spamcannibal.org': 'http://www.spamcannibal.org', + 'xbl.spamhaus.org': 'http://www.spamhaus.org/xbl/', + 'zen.spamhaus.org': 'http://www.spamhaus.org/zen/', + 'rbl.suresupport.com': 'http://suresupport.com/postmaster', + 'db.wpbl.info': 'http://www.wpbl.info', + 'sbl.spamhaus.org': 'http://www.spamhaus.org/sbl/', + 'http.dnsbl.sorbs.net': 'http://www.sorbs.net', + 'csi.cloudmark.com': 'http://www.cloudmark.com/en/products/cloudmark-sender-intelligence/index', + 'rbl.interserver.net': 'http://rbl.interserver.net', + 'ubl.unsubscore.com': 'http://www.lashback.com/blacklist/', + 'dnsbl.sorbs.net': 'http://www.sorbs.net', + 'virbl.bit.nl': 'http://virbl.bit.nl', + 'pbl.spamhaus.org': 'http://www.spamhaus.org/pbl/', + 'socks.dnsbl.sorbs.net': 'http://www.sorbs.net', + 'short.rbl.jp': 'http://www.rbl.jp', + 'dnsbl.dronebl.org': 'http://www.dronebl.org', + 'blackholes.mail-abuse.org': 'http://www.mail-abuse.com/lookup.html', + 'truncate.gbudb.net': 'http://www.gbudb.com/truncate/index.jsp', + 'dyna.spamrats.com': 'http://www.spamrats.com', + 'spamrbl.imp.ch': 'http://antispam.imp.ch', + 'spam.dnsbl.sorbs.net': 'http://www.sorbs.net', + 'wormrbl.imp.ch': 'http://antispam.imp.ch', + 'query.senderbase.org': 'http://www.senderbase.org/about', + 'opm.tornevall.org': 'http://dnsbl.tornevall.org', + 'netblock.pedantic.org': 'http://pedantic.org', + 'access.redhawk.org': 'http://www.redhawk.org/index.php?option=com_wrapper&Itemid=33', + 'cdl.anti-spam.org.cn': 'http://www.anti-spam.org.cn/?Locale=en_US', + 'multi.surbl.org': 'http://www.surbl.org', + 'noptr.spamrats.com': 'http://www.spamrats.com', + 'dnsbl.inps.de': 'http://dnsbl.inps.de/index.cgi?lang=en', + 'bl.spamcop.net': 'http://bl.spamcop.net', + 'cbl.abuseat.org': 'http://cbl.abuseat.org', + 'dsn.rfc-ignorant.org': 'http://www.rfc-ignorant.org/policy-dsn.php', + 'zombie.dnsbl.sorbs.net': 'http://www.sorbs.net', + 'dnsbl.njabl.org': 'http://dnsbl.njabl.org', + 'relays.mail-abuse.org': 'http://www.mail-abuse.com/lookup.html', + 'rbl.spamlab.com': 'http://tools.appriver.com/index.aspx?tool=rbl', + 'all.bl.blocklist.de': 'http://www.blocklist.de/en/rbldns.html' } + def handler(q=False): if q is False: return False @@ -89,11 +90,11 @@ def handler(q=False): return misperrors listed = [] info = [] - ipRev = '.'.join(ip.split('.')[::-1]) + ipRev = '.'.join(ip.split('.')[::-1]) for rbl in rbls: query = '{}.{}'.format(ipRev, rbl) try: - txt = resolver.query(query,'TXT') + txt = resolver.query(query, 'TXT') listed.append(query) info.append([str(t) for t in txt]) except Exception: @@ -101,9 +102,11 @@ def handler(q=False): result = "\n".join(["{}: {}".format(l, " - ".join(i)) for l, i in zip(listed, info)]) return {'results': [{'types': mispattributes.get('output'), 'values': result}]} + def introspection(): return mispattributes + def version(): moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/expansion/reversedns.py b/misp_modules/modules/expansion/reversedns.py index 46fe94a..3b945a7 100644 --- a/misp_modules/modules/expansion/reversedns.py +++ b/misp_modules/modules/expansion/reversedns.py @@ -1,5 +1,5 @@ import json -import dns.reversename, dns.resolver +from dns import reversename, resolver, exception misperrors = {'error': 'Error'} mispattributes = {'input': ['ip-src', 'ip-dst', 'domain|ip'], 'output': ['hostname']} @@ -12,6 +12,7 @@ moduleinfo = {'version': '0.1', 'author': 'Andreas Muehlemann', # config fields that your code expects from the site admin moduleconfig = ['nameserver'] + def handler(q=False): if q is False: return False @@ -26,9 +27,9 @@ def handler(q=False): return False # reverse lookup for ip - revname = dns.reversename.from_address(toquery) + revname = reversename.from_address(toquery) - r = dns.resolver.Resolver() + r = resolver.Resolver() r.timeout = 2 r.lifetime = 2 @@ -42,13 +43,13 @@ def handler(q=False): try: answer = r.query(revname, 'PTR') - except dns.resolver.NXDOMAIN: + except resolver.NXDOMAIN: misperrors['error'] = "NXDOMAIN" return misperrors - except dns.exception.Timeout: + except exception.Timeout: misperrors['error'] = "Timeout" return misperrors - except: + except Exception: misperrors['error'] = "DNS resolving error" return misperrors @@ -56,9 +57,11 @@ def handler(q=False): 'values':[str(answer[0])]}]} return r + def introspection(): return mispattributes + def version(): moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/expansion/securitytrails.py b/misp_modules/modules/expansion/securitytrails.py index 7df2c87..21ff089 100644 --- a/misp_modules/modules/expansion/securitytrails.py +++ b/misp_modules/modules/expansion/securitytrails.py @@ -114,8 +114,7 @@ def handle_domain(api, domain, misperrors): if r: result_filtered['results'].extend(r) else: - misperrors['error'] = misperrors[ - 'error'] + ' Error in expand History DNS' + misperrors['error'] = misperrors['error'] + ' Error in expand History DNS' return misperrors r, status_ok = expand_history_whois(api, domain) @@ -124,8 +123,7 @@ def handle_domain(api, domain, misperrors): if r: result_filtered['results'].extend(r) else: - misperrors['error'] = misperrors['error'] + \ - ' Error in expand History Whois' + misperrors['error'] = misperrors['error'] + ' Error in expand History Whois' return misperrors return result_filtered diff --git a/misp_modules/modules/expansion/sigma_queries.py b/misp_modules/modules/expansion/sigma_queries.py index d263245..37a8c14 100644 --- a/misp_modules/modules/expansion/sigma_queries.py +++ b/misp_modules/modules/expansion/sigma_queries.py @@ -1,4 +1,6 @@ -import sys, os, io, json +import sys +import io +import json try: from sigma.parser import SigmaCollectionParser from sigma.config import SigmaConfiguration @@ -13,6 +15,7 @@ moduleinfo = {'version': '0.1', 'author': 'Christian Studer', 'module-type': ['e 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 @@ -35,16 +38,18 @@ def handler(q=False): backend.finalize() print("#NEXT") targets.append(t) - except: + except Exception: continue sys.stdout = old_stdout results = result.getvalue()[:-5].split('#NEXT') - d_result = {t: r.strip() for t,r in zip(targets, results)} + 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 diff --git a/misp_modules/modules/expansion/sigma_syntax_validator.py b/misp_modules/modules/expansion/sigma_syntax_validator.py index e5cc335..b5fc0cf 100644 --- a/misp_modules/modules/expansion/sigma_syntax_validator.py +++ b/misp_modules/modules/expansion/sigma_syntax_validator.py @@ -12,6 +12,7 @@ moduleinfo = {'version': '0.1', 'author': 'Christian Studer', 'module-type': ['e 'description': 'An expansion hover module to perform a syntax check on sigma rules'} moduleconfig = [] + def handler(q=False): if q is False: return False @@ -27,9 +28,11 @@ def handler(q=False): result = ("Syntax error: {}".format(str(e))) return {'results': [{'types': mispattributes['output'], 'values': result}]} + def introspection(): return mispattributes + def version(): moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py b/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py index 78307a4..b87ab83 100644 --- a/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py +++ b/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py @@ -10,6 +10,7 @@ moduleinfo = {'version': '0.1', 'author': 'Christian Studer', 'module-type': ['e 'description': 'An expansion hover module to perform a syntax check on stix2 patterns.'} moduleconfig = [] + def handler(q=False): if q is False: return False @@ -27,16 +28,18 @@ def handler(q=False): if syntax_errors: s = 's' if len(syntax_errors) > 1 else '' s_errors = "" - for error in syntax_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 diff --git a/misp_modules/modules/expansion/threatcrowd.py b/misp_modules/modules/expansion/threatcrowd.py index 9187ca5..268832f 100644 --- a/misp_modules/modules/expansion/threatcrowd.py +++ b/misp_modules/modules/expansion/threatcrowd.py @@ -17,7 +17,7 @@ moduleconfig = [] # Avoid adding windows update to enrichment etc. def isBlacklisted(value): - blacklist = ['8.8.8.8', '255.255.255.255', '192.168.56.' , 'time.windows.com'] + blacklist = ['8.8.8.8', '255.255.255.255', '192.168.56.', 'time.windows.com'] for b in blacklist: if value in b: @@ -25,28 +25,31 @@ def isBlacklisted(value): return False + def valid_ip(ip): m = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", ip) return bool(m) and all(map(lambda n: 0 <= int(n) <= 255, m.groups())) + def valid_domain(hostname): if len(hostname) > 255: return False if hostname[-1] == ".": - hostname = hostname[:-1] # strip exactly one dot from the right, if present - allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?".format(bracket) + def handler(q=False): if q is False: return False @@ -212,6 +214,7 @@ def handler(q=False): exp_doc = "{}{}".format(export_doc.xml.get('header'), export_doc.xml.get('data')) return {'response': [], 'data': str(base64.b64encode(bytes(exp_doc, 'utf-8')), 'utf-8')} + def introspection(): modulesetup = {} try: @@ -236,6 +239,7 @@ def introspection(): pass return modulesetup + def version(): moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/export_mod/liteexport.py b/misp_modules/modules/export_mod/liteexport.py index 5d47489..e89c1c1 100755 --- a/misp_modules/modules/export_mod/liteexport.py +++ b/misp_modules/modules/export_mod/liteexport.py @@ -3,10 +3,10 @@ import base64 misperrors = {'error': 'Error'} -moduleinfo = {'version': '1', - 'author': 'TM', - 'description': 'export lite', - 'module-type': ['export']} +moduleinfo = {'version': '1', + 'author': 'TM', + 'description': 'export lite', + 'module-type': ['export']} moduleconfig = ["indent_json_export"] @@ -14,76 +14,75 @@ mispattributes = {} outputFileExtension = "json" responseType = "application/json" + def handler(q=False): - if q is False: - return False + if q is False: + return False - request = json.loads(q) + request = json.loads(q) - config = {} - if "config" in request: - config = request["config"] - else: - config = {"indent_json_export" : None} + config = {} + if "config" in request: + config = request["config"] + else: + config = {"indent_json_export": None} - if config['indent_json_export'] is not None: - try: - config['indent_json_export'] = int(config['indent_json_export']) - except: - config['indent_json_export'] = None + if config['indent_json_export'] is not None: + try: + config['indent_json_export'] = int(config['indent_json_export']) + except Exception: + config['indent_json_export'] = None - if 'data' not in request: - return False + if 'data' not in request: + return False - #~ Misp json structur - liteEvent = {'Event':{}} + # ~ Misp json structur + liteEvent = {'Event': {}} - for evt in request['data']: - rawEvent = evt['Event'] - liteEvent['Event']['info'] = rawEvent['info'] - liteEvent['Event']['Attribute'] = [] - - attrs = evt['Attribute'] - for attr in attrs: - if 'Internal reference' not in attr['category']: - liteAttr = {} - liteAttr['category'] = attr['category'] - liteAttr['type'] = attr['type'] - liteAttr['value'] = attr['value'] - liteEvent['Event']['Attribute'].append(liteAttr) + for evt in request['data']: + rawEvent = evt['Event'] + liteEvent['Event']['info'] = rawEvent['info'] + liteEvent['Event']['Attribute'] = [] + + attrs = evt['Attribute'] + for attr in attrs: + if 'Internal reference' not in attr['category']: + liteAttr = {} + liteAttr['category'] = attr['category'] + liteAttr['type'] = attr['type'] + liteAttr['value'] = attr['value'] + liteEvent['Event']['Attribute'].append(liteAttr) + + return {'response': [], + 'data': str(base64.b64encode(bytes( + json.dumps(liteEvent, indent=config['indent_json_export']), 'utf-8')), 'utf-8')} - return {'response' : [], - 'data' : str(base64.b64encode( - bytes( - json.dumps(liteEvent, indent=config['indent_json_export']), - 'utf-8')), - 'utf-8') - } def introspection(): - modulesetup = {} - try: - responseType - modulesetup['responseType'] = responseType - except NameError: - pass - try: - userConfig - modulesetup['userConfig'] = userConfig - except NameError: - pass - try: - outputFileExtension - modulesetup['outputFileExtension'] = outputFileExtension - except NameError: - pass - try: - inputSource - modulesetup['inputSource'] = inputSource - except NameError: - pass - return modulesetup + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + outputFileExtension + modulesetup['outputFileExtension'] = outputFileExtension + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup + def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/export_mod/osqueryexport.py b/misp_modules/modules/export_mod/osqueryexport.py index 084762e..ba98fe6 100755 --- a/misp_modules/modules/export_mod/osqueryexport.py +++ b/misp_modules/modules/export_mod/osqueryexport.py @@ -13,7 +13,7 @@ types_to_use = ['regkey', 'regkey|value', 'mutex', 'windows-service-displayname' userConfig = { -}; +} moduleconfig = [] inputSource = ['event'] @@ -26,6 +26,7 @@ moduleinfo = {'version': '1.0', 'author': 'Julien Bachmann, Hacknowledge', 'description': 'OSQuery query export module', 'module-type': ['export']} + def handle_regkey(value): rep = {'HKCU': 'HKEY_USERS\\%', 'HKLM': 'HKEY_LOCAL_MACHINE'} rep = dict((re.escape(k), v) for k, v in rep.items()) @@ -33,6 +34,7 @@ def handle_regkey(value): value = pattern.sub(lambda m: rep[re.escape(m.group(0))], value) return 'SELECT * FROM registry WHERE path LIKE \'%s\';' % value + def handle_regkeyvalue(value): key, value = value.split('|') rep = {'HKCU': 'HKEY_USERS\\%', 'HKLM': 'HKEY_LOCAL_MACHINE'} @@ -41,27 +43,33 @@ def handle_regkeyvalue(value): key = pattern.sub(lambda m: rep[re.escape(m.group(0))], key) return 'SELECT * FROM registry WHERE path LIKE \'%s\' AND data LIKE \'%s\';' % (key, value) + def handle_mutex(value): return 'SELECT * FROM winbaseobj WHERE object_name LIKE \'%s\';' % value + def handle_service(value): return 'SELECT * FROM services WHERE display_name LIKE \'%s\' OR name like \'%s\';' % (value, value) + def handle_yara(value): return 'not implemented yet, not sure it\'s easily feasible w/o dropping the sig on the hosts first' + def handle_scheduledtask(value): return 'SELECT * FROM scheduled_tasks WHERE name LIKE \'%s\';' % value + handlers = { - 'regkey' : handle_regkey, - 'regkey|value' : handle_regkeyvalue, - 'mutex' : handle_mutex, - 'windows-service-displayname' : handle_service, - 'windows-scheduled-task' : handle_scheduledtask, - 'yara' : handle_yara + 'regkey': handle_regkey, + 'regkey|value': handle_regkeyvalue, + 'mutex': handle_mutex, + 'windows-service-displayname': handle_service, + 'windows-scheduled-task': handle_scheduledtask, + 'yara': handle_yara } + def handler(q=False): if q is False: return False @@ -73,7 +81,7 @@ def handler(q=False): for attribute in event["Attribute"]: if attribute['type'] in types_to_use: output = output + handlers[attribute['type']](attribute['value']) + '\n' - r = {"response":[], "data":str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} + r = {"response": [], "data": str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} return r diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 2aeaec7..77a2e83 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -126,7 +126,7 @@ class ReportGenerator(): summary = a.value return title.format(internal_id=internal_id, title=self.misp_event.info, - summary=summary) + summary=summary) def asciidoc(self, lang='en'): self.report += self.title() diff --git a/misp_modules/modules/export_mod/testexport.py b/misp_modules/modules/export_mod/testexport.py index ed93228..1fc7ff7 100755 --- a/misp_modules/modules/export_mod/testexport.py +++ b/misp_modules/modules/export_mod/testexport.py @@ -1,13 +1,12 @@ import json import base64 -import csv misperrors = {'error': 'Error'} userConfig = { -}; +} moduleconfig = [] @@ -28,9 +27,9 @@ def handler(q=False): if q is False: return False r = {'results': []} - result = json.loads(q) - output = ''; # Insert your magic here! - r = {"data":base64.b64encode(output.encode('utf-8')).decode('utf-8')} + result = json.loads(q) # noqa + output = '' # Insert your magic here! + r = {"data": base64.b64encode(output.encode('utf-8')).decode('utf-8')} return r diff --git a/misp_modules/modules/export_mod/threatStream_misp_export.py b/misp_modules/modules/export_mod/threatStream_misp_export.py index 3fd88c8..a9f7f06 100755 --- a/misp_modules/modules/export_mod/threatStream_misp_export.py +++ b/misp_modules/modules/export_mod/threatStream_misp_export.py @@ -49,9 +49,7 @@ def handler(q=False): if q is False or not q: return False - request = json.loads(q) - response = io.StringIO() writer = csv.DictWriter(response, fieldnames=["value", "itype", "tags"]) diff --git a/misp_modules/modules/import_mod/__init__.py b/misp_modules/modules/import_mod/__init__.py index 5190abb..c3eea05 100644 --- a/misp_modules/modules/import_mod/__init__.py +++ b/misp_modules/modules/import_mod/__init__.py @@ -1,3 +1,3 @@ -from . import _vmray +from . import _vmray # noqa __all__ = ['vmray_import', 'ocr', 'cuckooimport', 'goamlimport', 'email_import', 'mispjson', 'openiocimport', 'threatanalyzer_import', 'csvimport'] diff --git a/misp_modules/modules/import_mod/csvimport.py b/misp_modules/modules/import_mod/csvimport.py index 5ccf287..0e42d94 100644 --- a/misp_modules/modules/import_mod/csvimport.py +++ b/misp_modules/modules/import_mod/csvimport.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- -import json, os, base64 +import json +import os +import base64 import pymisp misperrors = {'error': 'Error'} @@ -9,18 +11,19 @@ moduleinfo = {'version': '0.1', 'author': 'Christian Studer', moduleconfig = [] inputSource = ['file'] userConfig = {'header': { - 'type': 'String', - 'message': 'Define the header of the csv file, with types (included in MISP attribute types or attribute fields) separated by commas.\nFor fields that do not match these types, please use space or simply nothing between commas.\nFor instance: ip-src,domain, ,timestamp'}, - 'has_header':{ - 'type': 'Boolean', - 'message': 'Tick this box ONLY if there is a header line, NOT COMMENTED, in the file (which will be skipped atm).' - }} + 'type': 'String', + 'message': 'Define the header of the csv file, with types (included in MISP attribute types or attribute fields) separated by commas.\nFor fields that do not match these types, please use space or simply nothing between commas.\nFor instance: ip-src,domain, ,timestamp'}, + 'has_header': { + 'type': 'Boolean', + 'message': 'Tick this box ONLY if there is a header line, NOT COMMENTED, in the file (which will be skipped atm).' +}} duplicatedFields = {'mispType': {'mispComment': 'comment'}, 'attrField': {'attrComment': 'comment'}} attributesFields = ['type', 'value', 'category', 'to_ids', 'comment', 'distribution'] delimiters = [',', ';', '|', '/', '\t', ' '] + class CsvParser(): def __init__(self, header, has_header): self.header = header @@ -32,17 +35,17 @@ class CsvParser(): return_data = [] if self.fields_number == 1: for line in data: - l = line.split('#')[0].strip() - if l: - return_data.append(l) + line = line.split('#')[0].strip() + if line: + return_data.append(line) self.delimiter = None else: self.delimiter_count = dict([(d, 0) for d in delimiters]) for line in data: - l = line.split('#')[0].strip() - if l: - self.parse_delimiter(l) - return_data.append(l) + line = line.split('#')[0].strip() + if line: + self.parse_delimiter(line) + return_data.append(line) # find which delimiter is used self.delimiter = self.find_delimiter() self.data = return_data[1:] if self.has_header else return_data @@ -115,6 +118,7 @@ class CsvParser(): # return list of indexes of the misp types, list of the misp types, remaining fields that will be attribute fields return list2pop, misp, list(reversed(head)) + def handler(q=False): if q is False: return False @@ -138,6 +142,7 @@ def handler(q=False): r = {'results': csv_parser.attributes} return r + def introspection(): modulesetup = {} try: @@ -152,6 +157,7 @@ def introspection(): pass return modulesetup + def version(): moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/import_mod/cuckooimport.py b/misp_modules/modules/import_mod/cuckooimport.py index 193477f..fc0c30e 100755 --- a/misp_modules/modules/import_mod/cuckooimport.py +++ b/misp_modules/modules/import_mod/cuckooimport.py @@ -1,7 +1,5 @@ import json -import logging -import sys -import base64 +import base64 misperrors = {'error': 'Error'} userConfig = {} @@ -13,160 +11,163 @@ moduleinfo = {'version': '0.1', 'author': 'Victor van der Stoep', moduleconfig = [] + def handler(q=False): # Just in case we have no data if q is False: return False - + # The return value r = {'results': []} # Load up that JSON - q = json.loads(q) + q = json.loads(q) data = base64.b64decode(q.get("data")).decode('utf-8') - + # If something really weird happened if not data: return json.dumps({"success": 0}) - + data = json.loads(data) - - # Get characteristics of file + + # Get characteristics of file targetFile = data['target']['file'] - + # Process the inital binary - processBinary(r, targetFile, initial = True) - + processBinary(r, targetFile, initial=True) + # Get binary information for dropped files if(data.get('dropped')): for droppedFile in data['dropped']: - processBinary(r, droppedFile, dropped = True) - + processBinary(r, droppedFile, dropped=True) + # Add malscore to results - r["results"].append({ - "values": "Malscore: {} ".format(data['malscore']), + r["results"].append({ + "values": "Malscore: {} ".format(data['malscore']), "types": "comment", - "categories": "Payload delivery", + "categories": "Payload delivery", "comment": "Cuckoo analysis: MalScore" }) - + # Add virustotal data, if exists if(data.get('virustotal')): processVT(r, data['virustotal']) - + # Add network information, should be improved processNetwork(r, data['network']) - + # Add behavioral information processSummary(r, data['behavior']['summary']) - - # Return + + # Return return r + def processSummary(r, summary): - r["results"].append({ - "values": summary['mutexes'], + r["results"].append({ + "values": summary['mutexes'], "types": "mutex", - "categories": "Artifacts dropped", + "categories": "Artifacts dropped", "comment": "Cuckoo analysis: Observed mutexes" }) - + + def processVT(r, virustotal): category = "Antivirus detection" - comment = "VirusTotal analysis" - + comment = "VirusTotal analysis" + if(virustotal.get('permalink')): - r["results"].append({ - "values": virustotal['permalink'], + r["results"].append({ + "values": virustotal['permalink'], "types": "link", - "categories": category, + "categories": category, "comments": comment + " - Permalink" }) - + if(virustotal.get('total')): - r["results"].append({ + r["results"].append({ "values": "VirusTotal detection rate {}/{}".format( virustotal['positives'], virustotal['total'] - ), + ), "types": "comment", - "categories": category, - "comment": comment - }) - else: - r["results"].append({ - "values": "Sample not detected on VirusTotal", - "types": "comment", - "categories": category, + "categories": category, "comment": comment }) - + else: + r["results"].append({ + "values": "Sample not detected on VirusTotal", + "types": "comment", + "categories": category, + "comment": comment + }) + def processNetwork(r, network): category = "Network activity" - + for host in network['hosts']: - r["results"].append({ - "values": host['ip'], + r["results"].append({ + "values": host['ip'], "types": "ip-dst", - "categories": category, + "categories": category, "comment": "Cuckoo analysis: Observed network traffic" }) - -def processBinary(r, target, initial = False, dropped = False): - if(initial): + +def processBinary(r, target, initial=False, dropped=False): + if(initial): comment = "Cuckoo analysis: Initial file" category = "Payload delivery" elif(dropped): category = "Artifacts dropped" comment = "Cuckoo analysis: Dropped file" - - r["results"].append({ - "values": target['name'], + + r["results"].append({ + "values": target['name'], "types": "filename", - "categories": category, + "categories": category, "comment": comment }) - - r["results"].append({ - "values": target['md5'], + + r["results"].append({ + "values": target['md5'], "types": "md5", - "categories": category, + "categories": category, "comment": comment }) - - r["results"].append({ - "values": target['sha1'], + + r["results"].append({ + "values": target['sha1'], "types": "sha1", - "categories": category, + "categories": category, "comment": comment }) - - r["results"].append({ - "values": target['sha256'], + + r["results"].append({ + "values": target['sha256'], "types": "sha256", - "categories": category, + "categories": category, "comment": comment }) - - r["results"].append({ - "values": target['sha512'], + + r["results"].append({ + "values": target['sha512'], "types": "sha512", - "categories": category, + "categories": category, "comment": comment }) - + # todo : add file size? - + if(target.get('guest_paths')): - r["results"].append({ + r["results"].append({ "values": target['guest_paths'], "types": "filename", - "categories": "Payload installation", + "categories": "Payload installation", "comment": comment + " - Path" }) - + def introspection(): modulesetup = {} @@ -187,10 +188,11 @@ def version(): moduleinfo['config'] = moduleconfig return moduleinfo + if __name__ == '__main__': x = open('test.json', 'r') q = [] q['data'] = x.read() q = base64.base64encode(q) - + handler(q) diff --git a/misp_modules/modules/import_mod/email_import.py b/misp_modules/modules/import_mod/email_import.py index fa7d5dc..956f520 100644 --- a/misp_modules/modules/import_mod/email_import.py +++ b/misp_modules/modules/import_mod/email_import.py @@ -115,7 +115,7 @@ def handler(q=False): email_targets = set() for rec in received: try: - email_check = re.search("for\s(.*@.*);", rec).group(1) + email_check = re.search(r"for\s(.*@.*);", rec).group(1) email_check = email_check.strip(' <>') email_targets.add(parseaddr(email_check)[1]) except (AttributeError): @@ -166,7 +166,7 @@ def handler(q=False): for ext in zipped_files: if filename.endswith(ext) is True: zipped_filetype = True - if zipped_filetype == False: + if not zipped_filetype: try: attachment_files += get_zipped_contents(filename, attachment_data) except RuntimeError: # File is encrypted with a password @@ -294,7 +294,7 @@ def get_zip_passwords(message): # Grab any strings that are marked off by special chars marking_chars = [["\'", "\'"], ['"', '"'], ['[', ']'], ['(', ')']] for char_set in marking_chars: - regex = re.compile("""\{0}([^\{1}]*)\{1}""".format(char_set[0], char_set[1])) + regex = re.compile(r"""\{0}([^\{1}]*)\{1}""".format(char_set[0], char_set[1])) marked_off = re.findall(regex, raw_text) possible_passwords += marked_off @@ -397,6 +397,7 @@ def version(): moduleinfo['config'] = moduleconfig return moduleinfo + if __name__ == '__main__': with open('tests/test_no_attach.eml', 'r') as email_file: handler(q=email_file.read()) diff --git a/misp_modules/modules/import_mod/goamlimport.py b/misp_modules/modules/import_mod/goamlimport.py index ecb0a2d..7116b44 100644 --- a/misp_modules/modules/import_mod/goamlimport.py +++ b/misp_modules/modules/import_mod/goamlimport.py @@ -1,6 +1,7 @@ -import json, datetime, time, base64 +import json +import time +import base64 import xml.etree.ElementTree as ET -from collections import defaultdict from pymisp import MISPEvent, MISPObject misperrors = {'error': 'Error'} @@ -11,12 +12,12 @@ moduleconfig = [] mispattributes = {'inputSource': ['file'], 'output': ['MISP objects']} t_from_objects = {'nodes': ['from_person', 'from_account', 'from_entity'], - 'leaves': ['from_funds_code', 'from_country']} + 'leaves': ['from_funds_code', 'from_country']} t_to_objects = {'nodes': ['to_person', 'to_account', 'to_entity'], - 'leaves': ['to_funds_code', 'to_country']} + 'leaves': ['to_funds_code', 'to_country']} t_person_objects = {'nodes': ['addresses'], - 'leaves': ['first_name', 'middle_name', 'last_name', 'gender', 'title', 'mothers_name', 'birthdate', - 'passport_number', 'passport_country', 'id_number', 'birth_place', 'alias', 'nationality1']} + 'leaves': ['first_name', 'middle_name', 'last_name', 'gender', 'title', 'mothers_name', 'birthdate', + 'passport_number', 'passport_country', 'id_number', 'birth_place', 'alias', 'nationality1']} t_account_objects = {'nodes': ['signatory'], 'leaves': ['institution_name', 'institution_code', 'swift', 'branch', 'non_banking_insitution', 'account', 'currency_code', 'account_name', 'iban', 'client_number', 'opened', 'closed', @@ -51,7 +52,7 @@ t_account_mapping = {'misp_name': 'bank-account', 'institution_name': 'instituti t_person_mapping = {'misp_name': 'person', 'comments': 'text', 'first_name': 'first-name', 'middle_name': 'middle-name', 'last_name': 'last-name', 'title': 'title', 'mothers_name': 'mothers-name', 'alias': 'alias', - 'birthdate': 'date-of-birth', 'birth_place': 'place-of-birth', 'gender': 'gender','nationality1': 'nationality', + 'birthdate': 'date-of-birth', 'birth_place': 'place-of-birth', 'gender': 'gender', 'nationality1': 'nationality', 'passport_number': 'passport-number', 'passport_country': 'passport-country', 'ssn': 'social-security-number', 'id_number': 'identity-card-number'} @@ -73,6 +74,7 @@ goAMLmapping = {'from_account': t_account_mapping, 'to_account': t_account_mappi nodes_to_ignore = ['addresses', 'signatory'] relationship_to_keep = ['signatory', 't_from', 't_from_my_client', 't_to', 't_to_my_client', 'address'] + class GoAmlParser(): def __init__(self): self.misp_event = MISPEvent() @@ -145,6 +147,7 @@ class GoAmlParser(): to_country_attribute = {'object_relation': 'to-country', 'value': to_country} misp_object.add_attribute(**to_country_attribute) + def handler(q=False): if q is False: return False @@ -157,16 +160,18 @@ def handler(q=False): aml_parser = GoAmlParser() try: aml_parser.read_xml(data) - except: + except Exception: misperrors['error'] = "Impossible to read XML data" return misperrors aml_parser.parse_xml() r = {'results': [obj.to_json() for obj in aml_parser.misp_event.objects]} return r + def introspection(): return mispattributes + def version(): moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/import_mod/mispjson.py b/misp_modules/modules/import_mod/mispjson.py index f9d52ec..a9c2226 100755 --- a/misp_modules/modules/import_mod/mispjson.py +++ b/misp_modules/modules/import_mod/mispjson.py @@ -2,7 +2,7 @@ import json import base64 misperrors = {'error': 'Error'} -userConfig = { }; +userConfig = {} inputSource = ['file'] @@ -19,23 +19,24 @@ def handler(q=False): r = {'results': []} request = json.loads(q) try: - mfile = base64.b64decode(request["data"]).decode('utf-8') - misp = json.loads(mfile) - event = misp['response'][0]['Event'] - for a in event["Attribute"]: - tmp = {} - tmp["values"] = a["value"] - tmp["categories"] = a["category"] - tmp["types"] = a["type"] - tmp["to_ids"] = a["to_ids"] - tmp["comment"] = a["comment"] - if a.get("data"): - tmp["data"] = a["data"] - r['results'].append(tmp) - except: - pass + mfile = base64.b64decode(request["data"]).decode('utf-8') + misp = json.loads(mfile) + event = misp['response'][0]['Event'] + for a in event["Attribute"]: + tmp = {} + tmp["values"] = a["value"] + tmp["categories"] = a["category"] + tmp["types"] = a["type"] + tmp["to_ids"] = a["to_ids"] + tmp["comment"] = a["comment"] + if a.get("data"): + tmp["data"] = a["data"] + r['results'].append(tmp) + except Exception: + pass return r + def introspection(): modulesetup = {} try: @@ -55,6 +56,7 @@ def version(): moduleinfo['config'] = moduleconfig return moduleinfo + if __name__ == '__main__': x = open('test.json', 'r') r = handler(q=x.read()) diff --git a/misp_modules/modules/import_mod/ocr.py b/misp_modules/modules/import_mod/ocr.py index f14212b..fef0fd1 100755 --- a/misp_modules/modules/import_mod/ocr.py +++ b/misp_modules/modules/import_mod/ocr.py @@ -14,7 +14,7 @@ ch.setFormatter(formatter) log.addHandler(ch) misperrors = {'error': 'Error'} -userConfig = {}; +userConfig = {} inputSource = ['file'] @@ -55,17 +55,17 @@ def handler(q=False): if document.format == 'PDF': with document as pdf: # Get number of pages - pages=len(pdf.sequence) + pages = len(pdf.sequence) 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("Stitching page {}".format(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("Final image size is {}x{}".format(pdf.width, pdf.height*(p+1))) + log.debug("Final image size is {}x{}".format(pdf.width, pdf.height * (p + 1))) else: image = document @@ -78,7 +78,6 @@ def handler(q=False): misperrors['error'] = "Corrupt or not an image file." return misperrors - ocrized = image_to_string(im) freetext = {} @@ -107,6 +106,7 @@ def version(): moduleinfo['config'] = moduleconfig return moduleinfo + if __name__ == '__main__': x = open('test.json', 'r') handler(q=x.read()) diff --git a/misp_modules/modules/import_mod/testimport.py b/misp_modules/modules/import_mod/testimport.py index 02369c5..891b3a6 100755 --- a/misp_modules/modules/import_mod/testimport.py +++ b/misp_modules/modules/import_mod/testimport.py @@ -1,28 +1,27 @@ import json import base64 -import csv misperrors = {'error': 'Error'} userConfig = { - 'number1': { - 'type': 'Integer', - 'regex': '/^[0-4]$/i', - 'errorMessage': 'Expected a number in range [0-4]', - 'message': 'Column number used for value' - }, - 'some_string': { - 'type': 'String', - 'message': 'A text field' - }, - 'boolean_field': { - 'type': 'Boolean', - 'message': 'Boolean field test' - }, - 'comment': { - 'type': 'Integer', - 'message': 'Column number used for comment' - } - }; + 'number1': { + 'type': 'Integer', + 'regex': '/^[0-4]$/i', + 'errorMessage': 'Expected a number in range [0-4]', + 'message': 'Column number used for value' + }, + 'some_string': { + 'type': 'String', + 'message': 'A text field' + }, + 'boolean_field': { + 'type': 'Boolean', + 'message': 'Boolean field test' + }, + 'comment': { + 'type': 'Integer', + 'message': 'Column number used for comment' + } +} inputSource = ['file', 'paste'] @@ -39,8 +38,8 @@ def handler(q=False): r = {'results': []} request = json.loads(q) request["data"] = base64.b64decode(request["data"]) - fields = ["value", "category", "type", "comment"] - r = {"results":[{"values":["192.168.56.1"], "types":["ip-src"], "categories":["Network activity"]}]} + # fields = ["value", "category", "type", "comment"] + r = {"results": [{"values": ["192.168.56.1"], "types":["ip-src"], "categories": ["Network activity"]}]} return r diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index c5d8ba3..4ae1cd2 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -90,7 +90,7 @@ def handler(q=False): 'values': sample_filename, 'data': base64.b64encode(file_data).decode(), 'type': 'malware-sample', 'categories': ['Payload delivery', 'Artifacts dropped'], 'to_ids': True, 'comment': ''}) - except Exception as e: + except Exception: # no 'sample' in archive, might be an url analysis, just ignore pass @@ -118,7 +118,7 @@ def process_analysis_json(analysis_json): # 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']: # compensate for absurd behavior of the data format: if one entry = immediately the dict, if multiple entries = list containing dicts @@ -126,7 +126,7 @@ def process_analysis_json(analysis_json): for subsection in ['http_command', 'http_header']: if isinstance(connection_section_connection[subsection], dict): connection_section_connection[subsection] = [connection_section_connection[subsection]] - + 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'] @@ -171,7 +171,7 @@ def process_analysis_json(analysis_json): if ':' in val: try: val_port = int(val.split(':')[1]) - except ValueError as e: + except ValueError: val_port = False val_hostname = cleanup_hostname(val.split(':')[0]) val_ip = cleanup_ip(val.split(':')[0]) diff --git a/misp_modules/modules/import_mod/vmray_import.py b/misp_modules/modules/import_mod/vmray_import.py index 88caa8f..819ea66 100644 --- a/misp_modules/modules/import_mod/vmray_import.py +++ b/misp_modules/modules/import_mod/vmray_import.py @@ -105,8 +105,8 @@ def handler(q=False): url1 = "https://cloud.vmray.com/user/analysis/view?from_sample_id=%u" % sample_id url2 = "&id=%u" % analysis_id url3 = "&sub=%2Freport%2Foverview.html" - a_id["results"].append({ "values": url1 + url2 + url3, "types": "link" }) - vmray_results = {'results': vmray_results["results"] + a_id["results"] } + a_id["results"].append({"values": url1 + url2 + url3, "types": "link"}) + vmray_results = {'results': vmray_results["results"] + a_id["results"]} # Clean up (remove doubles) if vti_patterns_found: vmray_results = vmrayCleanup(vmray_results) @@ -117,7 +117,7 @@ def handler(q=False): else: misperrors['error'] = "Unable to fetch sample id %u" % (sample_id) return misperrors - except: + except Exception: misperrors['error'] = "Unable to access VMRay API" return misperrors else: @@ -267,7 +267,7 @@ def vmrayGeneric(el, attr="", attrpos=1): if content: if attr: # Some elements are put between \"\" ; replace them to single - content = content.replace("\"\"","\"") + content = content.replace("\"\"", "\"") content_split = content.split("\"") # Attributes are between open " and close "; so use > if len(content_split) > attrpos: From 623a871c64242b9b9eeec2deb120d65052572c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 11 Dec 2018 15:32:48 +0100 Subject: [PATCH 281/401] fix: remove tests on python 3.5 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fd39470..2b59712 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,6 @@ services: cache: pip python: - - "3.5" - - "3.5-dev" - "3.6" - "3.6-dev" - "3.7-dev" From 630ef4762e90bf56e34c1c883d97b47034b5b121 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Thu, 13 Dec 2018 09:30:57 +0100 Subject: [PATCH 282/401] chg: [intel471] module added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7a93d88..0449577 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [Farsight DNSDB Passive DNS](misp_modules/modules/expansion/farsight_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [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. +* [intel471](misp_modules/modules/expansion/intel471.py) - a model module to check against [Intel471](https://intel471.com). * [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 from [MAC address Vendor Lookup](https://macaddress.io). See [integration tutorial here](https://macaddress.io/integrations/MISP-module). From 7952a8d6e738d5497000d77acf2a515345ef4958 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Thu, 13 Dec 2018 10:19:23 +0100 Subject: [PATCH 283/401] chg: [doc] cannot type today --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0449577..4cbcc4c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [Farsight DNSDB Passive DNS](misp_modules/modules/expansion/farsight_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [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. -* [intel471](misp_modules/modules/expansion/intel471.py) - a model module to check against [Intel471](https://intel471.com). +* [intel471](misp_modules/modules/expansion/intel471.py) - an expansion module to get info from [Intel471](https://intel471.com). * [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 from [MAC address Vendor Lookup](https://macaddress.io). See [integration tutorial here](https://macaddress.io/integrations/MISP-module). From 5ab8b605bbfcab03328c47db90adceb4ed699a6a Mon Sep 17 00:00:00 2001 From: milkmix Date: Mon, 24 Dec 2018 14:39:25 +0100 Subject: [PATCH 284/401] first export feature: sha1 attributes nxql query --- misp_modules/modules/export_mod/__init__.py | 2 +- .../modules/export_mod/nexthinkexport.py | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100755 misp_modules/modules/export_mod/nexthinkexport.py diff --git a/misp_modules/modules/export_mod/__init__.py b/misp_modules/modules/export_mod/__init__.py index 7f1fa50..1affbd2 100644 --- a/misp_modules/modules/export_mod/__init__.py +++ b/misp_modules/modules/export_mod/__init__.py @@ -1,2 +1,2 @@ __all__ = ['cef_export', 'liteexport', 'goamlexport', 'threat_connect_export', 'pdfexport', - 'threatStream_misp_export', 'osqueryexport'] + 'threatStream_misp_export', 'osqueryexport', 'nexthinkexport'] diff --git a/misp_modules/modules/export_mod/nexthinkexport.py b/misp_modules/modules/export_mod/nexthinkexport.py new file mode 100755 index 0000000..92af800 --- /dev/null +++ b/misp_modules/modules/export_mod/nexthinkexport.py @@ -0,0 +1,86 @@ +""" +Export module for coverting MISP events into Nexthink NXQL queries. +Source: https://github.com/HacknowledgeCH/misp-modules/blob/master/misp_modules/modules/export_mod/nexthinkexport.py +Config['Period'] : allows to define period over witch to look for IOC from now (15m, 1d, 2w, 30d, ...) +""" + +import base64 +import json +import re + +misperrors = {"error": "Error"} + +types_to_use = ['sha1'] + +userConfig = { + +} + +moduleconfig = ["Period"] +inputSource = ['event'] + +outputFileExtension = 'conf' +responseType = 'application/txt' + + +moduleinfo = {'version': '1.0', 'author': 'Julien Bachmann, Hacknowledge', + 'description': 'Nexthink NXQL query export module', + 'module-type': ['export']} + + +def handle_sha1(value, period): + return ''' + (select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) + (from (binary user device execution) + (where binary (eq hash (sha1 %s)))) + (between now-%s now) + (limit 1000)) + ''' % (value, period) + +handlers = { + 'sha1': handle_sha1 +} + +def handler(q=False): + if q is False: + return False + r = {'results': []} + request = json.loads(q) + config = request.get("config", {"Period": ""}) + output = '' + + for event in request["data"]: + for attribute in event["Attribute"]: + if attribute['type'] in types_to_use: + output = output + handlers[attribute['type']](attribute['value'], config['Period']) + '\n' + r = {"response": [], "data": str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} + return r + + +def introspection(): + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + outputFileExtension + modulesetup['outputFileExtension'] = outputFileExtension + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 537f9132f518fc702eecc0adb03ce7cbeda9c446 Mon Sep 17 00:00:00 2001 From: milkmix Date: Mon, 24 Dec 2018 16:40:31 +0100 Subject: [PATCH 285/401] support for md5 and sha1 hashes --- .../modules/export_mod/nexthinkexport.py | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/export_mod/nexthinkexport.py b/misp_modules/modules/export_mod/nexthinkexport.py index 92af800..ee05923 100755 --- a/misp_modules/modules/export_mod/nexthinkexport.py +++ b/misp_modules/modules/export_mod/nexthinkexport.py @@ -10,7 +10,7 @@ import re misperrors = {"error": "Error"} -types_to_use = ['sha1'] +types_to_use = ['sha1', 'md5'] userConfig = { @@ -29,16 +29,26 @@ moduleinfo = {'version': '1.0', 'author': 'Julien Bachmann, Hacknowledge', def handle_sha1(value, period): - return ''' - (select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) - (from (binary user device execution) - (where binary (eq hash (sha1 %s)))) - (between now-%s now) - (limit 1000)) + query = '''select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) +(from (binary user device execution) +(where binary (eq sha1 (sha1 %s))) +(between now-%s now)) +(limit 1000) ''' % (value, period) + return query.replace('\n', ' ') + +def handle_md5(value, period): + query = '''select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) +(from (binary user device execution) +(where binary (eq hash (md5 %s))) +(between now-%s now)) +(limit 1000) + ''' % (value, period) + return query.replace('\n', ' ') handlers = { - 'sha1': handle_sha1 + 'sha1': handle_sha1, + 'md5': handle_md5 } def handler(q=False): @@ -56,7 +66,6 @@ def handler(q=False): r = {"response": [], "data": str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} return r - def introspection(): modulesetup = {} try: From b64c3e4bf4f2af06257cf196388429d3db959034 Mon Sep 17 00:00:00 2001 From: milkmix Date: Mon, 24 Dec 2018 17:07:45 +0100 Subject: [PATCH 286/401] added domain attributes support --- .../modules/export_mod/nexthinkexport.py | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/export_mod/nexthinkexport.py b/misp_modules/modules/export_mod/nexthinkexport.py index ee05923..eaa2f7a 100755 --- a/misp_modules/modules/export_mod/nexthinkexport.py +++ b/misp_modules/modules/export_mod/nexthinkexport.py @@ -1,7 +1,7 @@ """ Export module for coverting MISP events into Nexthink NXQL queries. Source: https://github.com/HacknowledgeCH/misp-modules/blob/master/misp_modules/modules/export_mod/nexthinkexport.py -Config['Period'] : allows to define period over witch to look for IOC from now (15m, 1d, 2w, 30d, ...) +Config['Period'] : allows to define period over witch to look for IOC from now (15m, 1d, 2w, 30d, ...), see Nexthink data model documentation """ import base64 @@ -10,7 +10,7 @@ import re misperrors = {"error": "Error"} -types_to_use = ['sha1', 'md5'] +types_to_use = ['sha1', 'sha256', 'md5', 'domain'] userConfig = { @@ -19,7 +19,7 @@ userConfig = { moduleconfig = ["Period"] inputSource = ['event'] -outputFileExtension = 'conf' +outputFileExtension = 'nxql' responseType = 'application/txt' @@ -27,7 +27,6 @@ moduleinfo = {'version': '1.0', 'author': 'Julien Bachmann, Hacknowledge', 'description': 'Nexthink NXQL query export module', 'module-type': ['export']} - def handle_sha1(value, period): query = '''select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) (from (binary user device execution) @@ -37,6 +36,15 @@ def handle_sha1(value, period): ''' % (value, period) return query.replace('\n', ' ') +def handle_sha256(value, period): + query = '''select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) +(from (binary user device execution) +(where binary (eq sha256 (sha256 %s))) +(between now-%s now)) +(limit 1000) + ''' % (value, period) + return query.replace('\n', ' ') + def handle_md5(value, period): query = '''select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) (from (binary user device execution) @@ -46,9 +54,21 @@ def handle_md5(value, period): ''' % (value, period) return query.replace('\n', ' ') +def handle_domain(value, period): + query = '''select ((device name) (device (name last_ip_address)) (user name)(user department) (binary executable_name)(binary application_name)(binary description)(binary application_category)(binary (executable_name version)) (binary #"Suspicious binary")(binary first_seen)(binary last_seen)(binary threat_level)(binary hash) (binary paths) +(destination name)(domain name) (domain domain_category)(domain hosting_country)(domain protocol)(domain threat_level) (port port_number)(web_request incoming_traffic)(web_request outgoing_traffic)) +(from (web_request device user binary executable destination domain port) +(where domain (eq name(string %s))) +(between now-%s now)) +(limit 1000) + ''' % (value, period) + return query.replace('\n', ' ') + handlers = { 'sha1': handle_sha1, - 'md5': handle_md5 + 'sha256': handle_sha256, + 'md5': handle_md5, + 'domain': handle_domain } def handler(q=False): From ae5747efd5a878efe7c137a5f88780befadccfa0 Mon Sep 17 00:00:00 2001 From: milkmix Date: Mon, 24 Dec 2018 17:18:31 +0100 Subject: [PATCH 287/401] added documentation --- doc/export_mod/nexthinkexport.json | 9 +++++++++ doc/logos/nexthink.svg | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 doc/export_mod/nexthinkexport.json create mode 100644 doc/logos/nexthink.svg diff --git a/doc/export_mod/nexthinkexport.json b/doc/export_mod/nexthinkexport.json new file mode 100644 index 0000000..182448c --- /dev/null +++ b/doc/export_mod/nexthinkexport.json @@ -0,0 +1,9 @@ +{ + "description": "Nexthink NXQL query export module", + "requirements": [], + "features": "This module export an event as Nexthink NXQL queries that can then be used in your own python3 tool or from wget/powershell", + "references": ["https://doc.nexthink.com/Documentation/Nexthink/latest/APIAndIntegrations/IntroducingtheWebAPIV2"], + "input": "MISP Event attributes", + "output": "Nexthink NXQL queries", + "logo": "logos/nexthink.svg" +} diff --git a/doc/logos/nexthink.svg b/doc/logos/nexthink.svg new file mode 100644 index 0000000..f18ba8f --- /dev/null +++ b/doc/logos/nexthink.svg @@ -0,0 +1,22 @@ + + + + nexthink + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file From 615a56f9bbb8e59cb38f82405cef14a15f93f5bb Mon Sep 17 00:00:00 2001 From: milkmix Date: Mon, 24 Dec 2018 17:32:47 +0100 Subject: [PATCH 288/401] removed unused re module --- misp_modules/modules/export_mod/nexthinkexport.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/export_mod/nexthinkexport.py b/misp_modules/modules/export_mod/nexthinkexport.py index eaa2f7a..0123808 100755 --- a/misp_modules/modules/export_mod/nexthinkexport.py +++ b/misp_modules/modules/export_mod/nexthinkexport.py @@ -6,7 +6,6 @@ Config['Period'] : allows to define period over witch to look for IOC from now ( import base64 import json -import re misperrors = {"error": "Error"} @@ -22,7 +21,6 @@ inputSource = ['event'] outputFileExtension = 'nxql' responseType = 'application/txt' - moduleinfo = {'version': '1.0', 'author': 'Julien Bachmann, Hacknowledge', 'description': 'Nexthink NXQL query export module', 'module-type': ['export']} @@ -36,6 +34,7 @@ def handle_sha1(value, period): ''' % (value, period) return query.replace('\n', ' ') + def handle_sha256(value, period): query = '''select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) (from (binary user device execution) @@ -45,6 +44,7 @@ def handle_sha256(value, period): ''' % (value, period) return query.replace('\n', ' ') + def handle_md5(value, period): query = '''select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) (from (binary user device execution) @@ -54,6 +54,7 @@ def handle_md5(value, period): ''' % (value, period) return query.replace('\n', ' ') + def handle_domain(value, period): query = '''select ((device name) (device (name last_ip_address)) (user name)(user department) (binary executable_name)(binary application_name)(binary description)(binary application_category)(binary (executable_name version)) (binary #"Suspicious binary")(binary first_seen)(binary last_seen)(binary threat_level)(binary hash) (binary paths) (destination name)(domain name) (domain domain_category)(domain hosting_country)(domain protocol)(domain threat_level) (port port_number)(web_request incoming_traffic)(web_request outgoing_traffic)) @@ -64,6 +65,7 @@ def handle_domain(value, period): ''' % (value, period) return query.replace('\n', ' ') + handlers = { 'sha1': handle_sha1, 'sha256': handle_sha256, @@ -86,6 +88,7 @@ def handler(q=False): r = {"response": [], "data": str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} return r + def introspection(): modulesetup = {} try: @@ -110,6 +113,7 @@ def introspection(): pass return modulesetup + def version(): moduleinfo['config'] = moduleconfig return moduleinfo From 02cdc11445e62766b80b913dde4613dcf9fe3217 Mon Sep 17 00:00:00 2001 From: milkmix Date: Wed, 26 Dec 2018 08:33:21 +0100 Subject: [PATCH 289/401] added 2 blank lines to comply w/ pep8 --- misp_modules/modules/export_mod/nexthinkexport.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misp_modules/modules/export_mod/nexthinkexport.py b/misp_modules/modules/export_mod/nexthinkexport.py index 0123808..f1a0d79 100755 --- a/misp_modules/modules/export_mod/nexthinkexport.py +++ b/misp_modules/modules/export_mod/nexthinkexport.py @@ -25,6 +25,7 @@ moduleinfo = {'version': '1.0', 'author': 'Julien Bachmann, Hacknowledge', 'description': 'Nexthink NXQL query export module', 'module-type': ['export']} + def handle_sha1(value, period): query = '''select ((binary (executable_name version)) (user (name)) (device (name last_ip_address)) (execution (binary_path start_time))) (from (binary user device execution) @@ -73,6 +74,7 @@ handlers = { 'domain': handle_domain } + def handler(q=False): if q is False: return False From 352860c34297f066caab27eca955d304b3a9028f Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Wed, 26 Dec 2018 12:19:27 +0100 Subject: [PATCH 290/401] chg: [doc] Nexthink export format added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4cbcc4c..e03f39e 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [GoAML export](misp_modules/modules/export_mod/goamlexport.py) module to export in [GoAML format](http://goaml.unodc.org/goaml/en/index.html). * [Lite Export](misp_modules/modules/export_mod/liteexport.py) module to export a lite event. * [Simple PDF export](misp_modules/modules/export_mod/pdfexport.py) module to export in PDF (required: asciidoctor-pdf). +* [Nexthink query format](misp_modules/modules/export_mod/nexthinkexport.py) module to export in Nexthink query format. * [ThreatConnect](misp_modules/modules/export_mod/threat_connect_export.py) module to export in ThreatConnect CSV format. * [ThreatStream](misp_modules/modules/export_mod/threatStream_misp_export.py) module to export in ThreatStream format. From c25ea545eebabb91501aafb63c3833d4de3201c5 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Wed, 26 Dec 2018 12:22:23 +0100 Subject: [PATCH 291/401] chg: [doc] osquery export module added. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e03f39e..3c6940d 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [Lite Export](misp_modules/modules/export_mod/liteexport.py) module to export a lite event. * [Simple PDF export](misp_modules/modules/export_mod/pdfexport.py) module to export in PDF (required: asciidoctor-pdf). * [Nexthink query format](misp_modules/modules/export_mod/nexthinkexport.py) module to export in Nexthink query format. +* [osquery](misp_modules/modules/export_mod/osqueryexport.py) module to export in [osquery](https://osquery.io/) query format. * [ThreatConnect](misp_modules/modules/export_mod/threat_connect_export.py) module to export in ThreatConnect CSV format. * [ThreatStream](misp_modules/modules/export_mod/threatStream_misp_export.py) module to export in ThreatStream format. From eeccecc0b2476392c7a102ea376830a047aa9619 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Wed, 26 Dec 2018 12:23:08 +0100 Subject: [PATCH 292/401] chg: [doc] Nexthink module added --- doc/documentation.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/documentation.md b/doc/documentation.md index 49fb6b5..ca1b1d9 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -1013,6 +1013,22 @@ Lite export of a MISP event. ----- +#### [nexthinkexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/nexthinkexport.py) + + + +Nexthink NXQL query export module +- **features**: +>This module export an event as Nexthink NXQL queries that can then be used in your own python3 tool or from wget/powershell +- **input**: +>MISP Event attributes +- **output**: +>Nexthink NXQL queries +- **references**: +>https://doc.nexthink.com/Documentation/Nexthink/latest/APIAndIntegrations/IntroducingtheWebAPIV2 + +----- + #### [osqueryexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/osqueryexport.py) From 96bd04774733998ea071548adfa5770d09759f74 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Wed, 26 Dec 2018 12:24:52 +0100 Subject: [PATCH 293/401] add: [doc] link documentation to README --- doc/README.md | 1 + 1 file changed, 1 insertion(+) create mode 120000 doc/README.md diff --git a/doc/README.md b/doc/README.md new file mode 120000 index 0000000..0963ae8 --- /dev/null +++ b/doc/README.md @@ -0,0 +1 @@ +documentation.md \ No newline at end of file From 77c37b7cd6357effc9181b9e591784c4a54bf39c Mon Sep 17 00:00:00 2001 From: Ruiwen Chua Date: Thu, 3 Jan 2019 15:10:39 +0800 Subject: [PATCH 294/401] fix: allow redis details to be retrieved from environment variables --- misp_modules/helpers/cache.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/misp_modules/helpers/cache.py b/misp_modules/helpers/cache.py index 77506fa..3ece927 100644 --- a/misp_modules/helpers/cache.py +++ b/misp_modules/helpers/cache.py @@ -19,12 +19,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import os + import redis import hashlib -port = 6379 -hostname = '127.0.0.1' -db = 5 +port = int(os.getenv("REDIS_PORT")) if os.getenv("REDIS_PORT") else 6379 +hostname = os.getenv("REDIS_BACKEND") or '127.0.0.1' +db = int(os.getenv("REDIS_DATABASE")) if os.getenv("REDIS_DATABASE") else 0 def selftest(enable=True): From 55f05e05249689ce83799ebe408bab54a61babf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Mon, 21 Jan 2019 13:31:52 +0100 Subject: [PATCH 295/401] chg: Use pipenv, update bgpranking/ipasn modules --- .travis.yml | 19 +- Pipfile | 45 ++ Pipfile.lock | 735 ++++++++++++++++++ README.md | 2 +- REQUIREMENTS | 94 ++- doc/documentation.md | 26 +- doc/expansion/asn_history.json | 8 - doc/expansion/bgpranking.json | 8 + doc/expansion/ipasn.json | 10 +- doc/generate_documentation.py | 6 +- misp_modules/helpers/cache.py | 10 +- .../{asn_history.py => bgpranking.py} | 20 +- misp_modules/modules/expansion/ipasn.py | 19 +- setup.py | 15 +- 14 files changed, 896 insertions(+), 121 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 doc/expansion/asn_history.json create mode 100644 doc/expansion/bgpranking.json rename misp_modules/modules/expansion/{asn_history.py => bgpranking.py} (50%) diff --git a/.travis.yml b/.travis.yml index 2b59712..b574d4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,25 +11,24 @@ python: - "3.7-dev" install: - - pip install -U nose codecov pytest flake8 - - pip install -U -r REQUIREMENTS - - pip install . + - pip install pipenv + - pipenv install --dev script: - - coverage run -m --parallel-mode --source=misp_modules misp_modules.__init__ -l 127.0.0.1 & + - pipenv run coverage run -m --parallel-mode --source=misp_modules misp_modules.__init__ -l 127.0.0.1 & - pid=$! - sleep 5 - - nosetests --with-coverage --cover-package=misp_modules + - pipenv run nosetests --with-coverage --cover-package=misp_modules - kill -s INT $pid - pushd ~/ - - coverage run -m --parallel-mode --source=misp_modules misp_modules.__init__ -s -l 127.0.0.1 & + - pipenv run coverage run -m --parallel-mode --source=misp_modules misp_modules.__init__ -s -l 127.0.0.1 & - pid=$! - popd - sleep 5 - - nosetests --with-coverage --cover-package=misp_modules + - pipenv run nosetests --with-coverage --cover-package=misp_modules - kill -s INT $pid - - flake8 --ignore=E501,W503 misp_modules + - pipenv run flake8 --ignore=E501,W503 misp_modules after_success: - - coverage combine .coverage* - - codecov + - pipenv run coverage combine .coverage* + - pipenv run codecov diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..c086e62 --- /dev/null +++ b/Pipfile @@ -0,0 +1,45 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +nose = "*" +codecov = "*" +pytest = "*" +flake8 = "*" + +[packages] +dnspython = "*" +requests = "*" +urlarchiver = "*" +passivetotal = "*" +pypdns = "*" +pypssl = "*" +pyeupi = "*" +uwhois = {editable = true,git = "https://github.com/Rafiot/uwhoisd.git",ref = "testing",subdirectory = "client"} +pymisp = {editable = true,git = "https://github.com/MISP/PyMISP.git"} +pyonyphe = {editable = true,git = "https://github.com/sebdraven/pyonyphe"} +pydnstrails = {editable = true,git = "https://github.com/sebdraven/pydnstrails"} +pytesseract = "*" +pygeoip = "*" +beautifulsoup4 = "*" +oauth2 = "*" +yara-python = ">=3.8.0" +sigmatools = "*" +stix2-patterns = "*" +maclookup = "*" +vulners = "*" +blockchain = "*" +pyintel471 = {editable = true,git = "https://github.com/MISP/PyIntel471.git"} +shodan = "*" +Pillow = "*" +Wand = "*" +SPARQLWrapper = "*" +domaintools_api = "*" +misp-modules = {editable = true,path = "."} +pybgpranking = {editable = true,git = "https://github.com/D4-project/BGP-Ranking.git/",subdirectory = "client"} +pyipasnhistory = {editable = true,git = "https://github.com/D4-project/IPASN-History.git/",subdirectory = "client"} + +[requires] +python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..02a61d1 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,735 @@ +{ + "_meta": { + "hash": { + "sha256": "f501a84bdd41ca21a2af020278ce030985cccd5f2f5683cd075797be4523587d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aiohttp": { + "hashes": [ + "sha256:0419705a36b43c0ac6f15469f9c2a08cad5c939d78bd12a5c23ea167c8253b2b", + "sha256:1812fc4bc6ac1bde007daa05d2d0f61199324e0cc893b11523e646595047ca08", + "sha256:2214b5c0153f45256d5d52d1e0cafe53f9905ed035a142191727a5fb620c03dd", + "sha256:275909137f0c92c61ba6bb1af856a522d5546f1de8ea01e4e726321c697754ac", + "sha256:3983611922b561868428ea1e7269e757803713f55b53502423decc509fef1650", + "sha256:51afec6ffa50a9da4cdef188971a802beb1ca8e8edb40fa429e5e529db3475fa", + "sha256:589f2ec8a101a0f340453ee6945bdfea8e1cd84c8d88e5be08716c34c0799d95", + "sha256:789820ddc65e1f5e71516adaca2e9022498fa5a837c79ba9c692a9f8f916c330", + "sha256:7a968a0bdaaf9abacc260911775611c9a602214a23aeb846f2eb2eeaa350c4dc", + "sha256:7aeefbed253f59ea39e70c5848de42ed85cb941165357fc7e87ab5d8f1f9592b", + "sha256:7b2eb55c66512405103485bd7d285a839d53e7fdc261ab20e5bcc51d7aaff5de", + "sha256:87bc95d3d333bb689c8d755b4a9d7095a2356108002149523dfc8e607d5d32a4", + "sha256:9d80e40db208e29168d3723d1440ecbb06054d349c5ece6a2c5a611490830dd7", + "sha256:a1b442195c2a77d33e4dbee67c9877ccbdd3a1f686f91eb479a9577ed8cc326b", + "sha256:ab3d769413b322d6092f169f316f7b21cd261a7589f7e31db779d5731b0480d8", + "sha256:b066d3dec5d0f5aee6e34e5765095dc3d6d78ef9839640141a2b20816a0642bd", + "sha256:b24e7845ae8de3e388ef4bcfcf7f96b05f52c8e633b33cf8003a6b1d726fc7c2", + "sha256:c59a953c3f8524a7c86eaeaef5bf702555be12f5668f6384149fe4bb75c52698", + "sha256:cf2cc6c2c10d242790412bea7ccf73726a9a44b4c4b073d2699ef3b48971fd95", + "sha256:e0c9c8d4150ae904f308ff27b35446990d2b1dfc944702a21925937e937394c6", + "sha256:f1839db4c2b08a9c8f9788112644f8a8557e8e0ecc77b07091afabb941dc55d0", + "sha256:f3df52362be39908f9c028a65490fae0475e4898b43a03d8aa29d1e765b45e07" + ], + "version": "==3.4.4" + }, + "antlr4-python3-runtime": { + "hashes": [ + "sha256:168cdcec8fb9152e84a87ca6fd261b3d54c8f6358f42ab3b813b14a7193bb50b" + ], + "markers": "python_version >= '3'", + "version": "==4.7.2" + }, + "async-timeout": { + "hashes": [ + "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", + "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" + ], + "version": "==3.0.1" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, + "beautifulsoup4": { + "hashes": [ + "sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858", + "sha256:945065979fb8529dd2f37dbb58f00b661bdbcbebf954f93b32fdf5263ef35348", + "sha256:ba6d5c59906a85ac23dadfe5c88deaf3e179ef565f4898671253e50a78680718" + ], + "index": "pypi", + "version": "==4.7.1" + }, + "blockchain": { + "hashes": [ + "sha256:dbaa3eebb6f81b4245005739da802c571b09f98d97eb66520afd95d9ccafebe2" + ], + "index": "pypi", + "version": "==1.4.4" + }, + "certifi": { + "hashes": [ + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + ], + "version": "==2018.11.29" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "click-plugins": { + "hashes": [ + "sha256:b1ee1ccc9421c73007fe290680d97984eb6eaf5f4512b7620c6aa46031d6cb6b", + "sha256:dfed74b5063546a137de99baaaf742b4de4337ad2b3e1df5ec7c8a256adc0847" + ], + "version": "==1.0.4" + }, + "colorama": { + "hashes": [ + "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", + "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48" + ], + "version": "==0.4.1" + }, + "dnspython": { + "hashes": [ + "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", + "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" + ], + "index": "pypi", + "version": "==1.16.0" + }, + "domaintools-api": { + "hashes": [ + "sha256:f567f407b8997e947df5badf7c2bea64fdfd33c54ade24eab36ef575fb71ccb7" + ], + "index": "pypi", + "version": "==0.3.3" + }, + "enum-compat": { + "hashes": [ + "sha256:939ceff18186a5762ae4db9fa7bfe017edbd03b66526b798dd8245394c8a4192" + ], + "version": "==0.0.2" + }, + "ez-setup": { + "hashes": [ + "sha256:303c5b17d552d1e3fb0505d80549f8579f557e13d8dc90e5ecef3c07d7f58642" + ], + "version": "==0.9" + }, + "future": { + "hashes": [ + "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8" + ], + "version": "==0.17.1" + }, + "httplib2": { + "hashes": [ + "sha256:f61fb838a94ce3b349aa32c92fd8430f7e3511afdb18bf9640d647e30c90a6d6" + ], + "version": "==0.12.0" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "idna-ssl": { + "hashes": [ + "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c" + ], + "markers": "python_version < '3.7'", + "version": "==1.1.0" + }, + "isodate": { + "hashes": [ + "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", + "sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81" + ], + "version": "==0.6.0" + }, + "jsonschema": { + "hashes": [ + "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", + "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" + ], + "version": "==2.6.0" + }, + "maclookup": { + "hashes": [ + "sha256:33bf8eaebe3b1e4ab4ae9277dd93c78024e0ebf6b3c42f76c37695bc26ce287a", + "sha256:795e792cd3e03c9bdad77e52904d43ff71d3ac03b360443f99d4bae08a6bffef" + ], + "index": "pypi", + "version": "==1.0.3" + }, + "misp-modules": { + "editable": true, + "path": "." + }, + "multidict": { + "hashes": [ + "sha256:024b8129695a952ebd93373e45b5d341dbb87c17ce49637b34000093f243dd4f", + "sha256:041e9442b11409be5e4fc8b6a97e4bcead758ab1e11768d1e69160bdde18acc3", + "sha256:045b4dd0e5f6121e6f314d81759abd2c257db4634260abcfe0d3f7083c4908ef", + "sha256:047c0a04e382ef8bd74b0de01407e8d8632d7d1b4db6f2561106af812a68741b", + "sha256:068167c2d7bbeebd359665ac4fff756be5ffac9cda02375b5c5a7c4777038e73", + "sha256:148ff60e0fffa2f5fad2eb25aae7bef23d8f3b8bdaf947a65cdbe84a978092bc", + "sha256:1d1c77013a259971a72ddaa83b9f42c80a93ff12df6a4723be99d858fa30bee3", + "sha256:1d48bc124a6b7a55006d97917f695effa9725d05abe8ee78fd60d6588b8344cd", + "sha256:31dfa2fc323097f8ad7acd41aa38d7c614dd1960ac6681745b6da124093dc351", + "sha256:34f82db7f80c49f38b032c5abb605c458bac997a6c3142e0d6c130be6fb2b941", + "sha256:3d5dd8e5998fb4ace04789d1d008e2bb532de501218519d70bb672c4c5a2fc5d", + "sha256:4a6ae52bd3ee41ee0f3acf4c60ceb3f44e0e3bc52ab7da1c2b2aa6703363a3d1", + "sha256:4b02a3b2a2f01d0490dd39321c74273fed0568568ea0e7ea23e02bd1fb10a10b", + "sha256:4b843f8e1dd6a3195679d9838eb4670222e8b8d01bc36c9894d6c3538316fa0a", + "sha256:5de53a28f40ef3c4fd57aeab6b590c2c663de87a5af76136ced519923d3efbb3", + "sha256:61b2b33ede821b94fa99ce0b09c9ece049c7067a33b279f343adfe35108a4ea7", + "sha256:6a3a9b0f45fd75dc05d8e93dc21b18fc1670135ec9544d1ad4acbcf6b86781d0", + "sha256:76ad8e4c69dadbb31bad17c16baee61c0d1a4a73bed2590b741b2e1a46d3edd0", + "sha256:7ba19b777dc00194d1b473180d4ca89a054dd18de27d0ee2e42a103ec9b7d014", + "sha256:7c1b7eab7a49aa96f3db1f716f0113a8a2e93c7375dd3d5d21c4941f1405c9c5", + "sha256:7fc0eee3046041387cbace9314926aa48b681202f8897f8bff3809967a049036", + "sha256:8ccd1c5fff1aa1427100ce188557fc31f1e0a383ad8ec42c559aabd4ff08802d", + "sha256:8e08dd76de80539d613654915a2f5196dbccc67448df291e69a88712ea21e24a", + "sha256:c18498c50c59263841862ea0501da9f2b3659c00db54abfbf823a80787fde8ce", + "sha256:c49db89d602c24928e68c0d510f4fcf8989d77defd01c973d6cbe27e684833b1", + "sha256:ce20044d0317649ddbb4e54dab3c1bcc7483c78c27d3f58ab3d0c7e6bc60d26a", + "sha256:d1071414dd06ca2eafa90c85a079169bfeb0e5f57fd0b45d44c092546fcd6fd9", + "sha256:d3be11ac43ab1a3e979dac80843b42226d5d3cccd3986f2e03152720a4297cd7", + "sha256:db603a1c235d110c860d5f39988ebc8218ee028f07a7cbc056ba6424372ca31b" + ], + "version": "==4.5.2" + }, + "oauth2": { + "hashes": [ + "sha256:15b5c42301f46dd63113f1214b0d81a8b16254f65a86d3c32a1b52297f3266e6", + "sha256:c006a85e7c60107c7cc6da1b184b5c719f6dd7202098196dfa6e55df669b59bf" + ], + "index": "pypi", + "version": "==1.9.0.post1" + }, + "passivetotal": { + "hashes": [ + "sha256:d745a6519ec04e3a354682978ebf07778bf7602beac30307cbad075ff1a4418d" + ], + "index": "pypi", + "version": "==1.0.30" + }, + "pillow": { + "hashes": [ + "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", + "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", + "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", + "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", + "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", + "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", + "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", + "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", + "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", + "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", + "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", + "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", + "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", + "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", + "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", + "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", + "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", + "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", + "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", + "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", + "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", + "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", + "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", + "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", + "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", + "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", + "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", + "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", + "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", + "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" + ], + "index": "pypi", + "version": "==5.4.1" + }, + "psutil": { + "hashes": [ + "sha256:1c19957883e0b93d081d41687089ad630e370e26dc49fd9df6951d6c891c4736", + "sha256:1c71b9716790e202a00ab0931a6d1e25db1aa1198bcacaea2f5329f75d257fff", + "sha256:3b7a4daf4223dae171a67a89314ac5ca0738e94064a78d99cfd751c55d05f315", + "sha256:3e19be3441134445347af3767fa7770137d472a484070840eee6653b94ac5576", + "sha256:6e265c8f3da00b015d24b842bfeb111f856b13d24f2c57036582568dc650d6c3", + "sha256:809c9cef0402e3e48b5a1dddc390a8a6ff58b15362ea5714494073fa46c3d293", + "sha256:b4d1b735bf5b120813f4c89db8ac22d89162c558cbd7fdd298866125fe906219", + "sha256:bbffac64cfd01c6bcf90eb1bedc6c80501c4dae8aef4ad6d6dd49f8f05f6fc5a", + "sha256:bfcea4f189177b2d2ce4a34b03c4ac32c5b4c22e21f5b093d9d315e6e253cd81" + ], + "version": "==5.4.8" + }, + "pybgpranking": { + "editable": true, + "git": "https://github.com/D4-project/BGP-Ranking.git/", + "ref": "7e698f87366e6f99b4d0d11852737db28e3ddc62", + "subdirectory": "client" + }, + "pydnstrails": { + "editable": true, + "git": "https://github.com/sebdraven/pydnstrails", + "ref": "48c1f740025c51289f43a24863d1845ff12fd21a" + }, + "pyeupi": { + "hashes": [ + "sha256:35b0e6b430f23ecd303f7cc7a8fe5147cf2509a5b2254eaf9695392c0af02901" + ], + "index": "pypi", + "version": "==1.0" + }, + "pygeoip": { + "hashes": [ + "sha256:1938b9dac7b00d77f94d040b9465ea52c938f3fcdcd318b5537994f3c16aef96", + "sha256:f22c4e00ddf1213e0fae36dc60b46ee7c25a6339941ec1a975539014c1f9a96d" + ], + "index": "pypi", + "version": "==0.3.2" + }, + "pyintel471": { + "editable": true, + "git": "https://github.com/MISP/PyIntel471.git", + "ref": "0df8d51f1c1425de66714b3a5a45edb69b8cc2fc" + }, + "pyipasnhistory": { + "editable": true, + "git": "https://github.com/D4-project/IPASN-History.git/", + "ref": "e846cd36fe1ed6b22f60890bba89f84e61b62e59", + "subdirectory": "client" + }, + "pymisp": { + "editable": true, + "git": "https://github.com/MISP/PyMISP.git", + "ref": "d4934cdf5f537c9f42ae37be7878de1848961de0" + }, + "pyonyphe": { + "editable": true, + "git": "https://github.com/sebdraven/pyonyphe", + "ref": "66329baeee7cab844f2203c047c2551828eaf14d" + }, + "pyparsing": { + "hashes": [ + "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a", + "sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3" + ], + "version": "==2.3.1" + }, + "pypdns": { + "hashes": [ + "sha256:0356360156dd26d2cf27a415a10ff2bd1ff1d2eb3b2dd51b35553d60b87fd328" + ], + "index": "pypi", + "version": "==1.3" + }, + "pypssl": { + "hashes": [ + "sha256:4dbe772aefdf4ab18934d83cde79e2fc5d5ba9d2b4153dc419a63faab3432643" + ], + "index": "pypi", + "version": "==2.1" + }, + "pytesseract": { + "hashes": [ + "sha256:11c20321595b6e2e904b594633edf1a717212b13bac7512986a2d807b8849770" + ], + "index": "pypi", + "version": "==0.2.6" + }, + "python-dateutil": { + "hashes": [ + "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93", + "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02" + ], + "version": "==2.7.5" + }, + "pyyaml": { + "hashes": [ + "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", + "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", + "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", + "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", + "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", + "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", + "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", + "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", + "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", + "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", + "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" + ], + "version": "==3.13" + }, + "rdflib": { + "hashes": [ + "sha256:58d5994610105a457cff7fdfe3d683d87786c5028a45ae032982498a7e913d6f", + "sha256:da1df14552555c5c7715d8ce71c08f404c988c58a1ecd38552d0da4fc261280d" + ], + "version": "==4.2.2" + }, + "redis": { + "hashes": [ + "sha256:2100750629beff143b6a200a2ea8e719fcf26420adabb81402895e144c5083cf", + "sha256:8e0bdd2de02e829b6225b25646f9fb9daffea99a252610d040409a6738541f0a" + ], + "version": "==3.0.1" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "requests-cache": { + "hashes": [ + "sha256:e9270030becc739b0a7f7f834234c73a878b2d794122bf76f40055a22419eb67", + "sha256:fe561ca119879bbcfb51f03a35e35b425e18f338248e59fd5cf2166c77f457a2" + ], + "version": "==0.4.13" + }, + "shodan": { + "hashes": [ + "sha256:c40abb6ff2fd66bdee9f773746fb961eefdfaa8e720a07cb12fb70def136268d" + ], + "index": "pypi", + "version": "==1.10.4" + }, + "sigmatools": { + "hashes": [ + "sha256:98c9897f27e7c99f398bff537bb6b0259599177d955f8b60a22db1b246f9cb0b" + ], + "index": "pypi", + "version": "==0.7.1" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "soupsieve": { + "hashes": [ + "sha256:10687fc53eeb3518e01a0ac84d3d711da623d3298a3039459d3f649927c4a270", + "sha256:b23a0d7da0247200fe83c67c34de9d7599ad404106367313d8e65e04174d0b4b" + ], + "version": "==1.7.2" + }, + "sparqlwrapper": { + "hashes": [ + "sha256:2a95fdede2833be660b81092934c4a0054ff85f2693098556762a2759ea486f1", + "sha256:7f4c8d38ea1bfcffbc358c9a05de35a3fd7152cc3e8ea57963ee7a0a242f7a5e", + "sha256:acf6d60f0a3684cb673653b07871acb0c350a974b891f20f8ac94926ff9eb2ff" + ], + "index": "pypi", + "version": "==1.8.2" + }, + "stix2-patterns": { + "hashes": [ + "sha256:137cbe28d29af774d526a49d60b3a40af7c19fe1e5f252e741bb25f253d5616f" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "tornado": { + "hashes": [ + "sha256:0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d", + "sha256:4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409", + "sha256:732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f", + "sha256:8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f", + "sha256:8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5", + "sha256:d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb", + "sha256:e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444" + ], + "version": "==5.1.1" + }, + "url-normalize": { + "hashes": [ + "sha256:3468d64cb22a9092a2c086e46c781f741dc9a1689b24e9b48ab5e8244ffa6c02", + "sha256:51e0f14050c79e732d175c33d12167f5e642cc23e0cb23275236af843faf884f" + ], + "version": "==1.4.1" + }, + "urlarchiver": { + "hashes": [ + "sha256:652e0890dab58bf62a759656671dcfb9a40eb4a77aac8a8d93154f00360238b5" + ], + "index": "pypi", + "version": "==0.2" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + }, + "uwhois": { + "editable": true, + "git": "https://github.com/Rafiot/uwhoisd.git", + "ref": "f6f035e52213c8abc20f2084d28cfffb399457cb", + "subdirectory": "client" + }, + "vulners": { + "hashes": [ + "sha256:8b468db8f8b0bad39ae51ebd4247f6ead90b6f53699e03b91ff9d63da70554d7", + "sha256:ad72378c842096cad9ebf83aa53d330117ece5d208ed7c419a21c70a8d5e2236", + "sha256:ffc92a099eeddea840fd199665992c0eb6d7ad69ac3a6730a286d00600bc5f2c" + ], + "index": "pypi", + "version": "==1.3.6" + }, + "wand": { + "hashes": [ + "sha256:3e59e4bda9ef9d643d90e881cc950c8eee1508ec2cde1c150a1cbd5a12c1c007", + "sha256:52763dbf65d00cf98d7bc910b49329eea15896249c5555d47e169f2b6efbe166" + ], + "index": "pypi", + "version": "==0.5.0" + }, + "xlsxwriter": { + "hashes": [ + "sha256:7cc07619760641b67112dbe0df938399d4d915d9b9924bb58eb5c17384d29cc6", + "sha256:ae22658a0fc5b9e875fa97c213d1ffd617d86dc49bf08be99ebdac814db7bf36" + ], + "version": "==1.1.2" + }, + "yara-python": { + "hashes": [ + "sha256:03e5c5e333c8572e7994b0b11964d515d61a393f23c5e272f8d0e4229f368c58", + "sha256:0423e08bd618752a028ac0405ff8e0103f3a8fd607dde7618a64a4c010c3757b", + "sha256:0a0dd632dcdb347d1a9a8b1f6a83b3a77d5e63f691357ea4021fb1cf1d7ff0a4", + "sha256:728b99627a8072a877eaaa4dafb4eff39d1b14ff4fd70d39f18899ce81e29625", + "sha256:7cb0d5724eccfa52e1bcd352a56cb4dc422aa51f5f6d0945d4f830783927513b", + "sha256:8c76531e89806c0309586dd4863a972d12f1d5d63261c6d4b9331a99859fd1d8", + "sha256:9472676583e212bc4e17c2236634e02273d53c872b350f0571b48e06183de233", + "sha256:9735b680a7d95c1d3f255c351bb067edc62cdb3c0999f7064278cb2c85245405", + "sha256:997f104590167220a9af5564c042ec4d6534261e7b8a5b49655d8dffecc6b8a2", + "sha256:a48e071d02a3699363e628ac899b5b7237803bcb4b512c92ebcb4fb9b1488497", + "sha256:b67c0d75a6519ca357b4b85ede9768c96a81fff20fbc169bd805ff009ddee561" + ], + "index": "pypi", + "version": "==3.8.1" + }, + "yarl": { + "hashes": [ + "sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9", + "sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f", + "sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb", + "sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320", + "sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842", + "sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0", + "sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829", + "sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310", + "sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4", + "sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8", + "sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1" + ], + "version": "==1.3.0" + } + }, + "develop": { + "atomicwrites": { + "hashes": [ + "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", + "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + ], + "version": "==1.2.1" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, + "certifi": { + "hashes": [ + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + ], + "version": "==2018.11.29" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "codecov": { + "hashes": [ + "sha256:8ed8b7c6791010d359baed66f84f061bba5bd41174bf324c31311e8737602788", + "sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83ef20507732bed4" + ], + "index": "pypi", + "version": "==2.0.15" + }, + "coverage": { + "hashes": [ + "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", + "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", + "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", + "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", + "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", + "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", + "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", + "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", + "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", + "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", + "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", + "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", + "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", + "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", + "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", + "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", + "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", + "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", + "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", + "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", + "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", + "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", + "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", + "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", + "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", + "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", + "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", + "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", + "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", + "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", + "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + ], + "version": "==4.5.2" + }, + "flake8": { + "hashes": [ + "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", + "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" + ], + "index": "pypi", + "version": "==3.6.0" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "more-itertools": { + "hashes": [ + "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", + "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", + "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" + ], + "version": "==5.0.0" + }, + "nose": { + "hashes": [ + "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", + "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", + "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98" + ], + "index": "pypi", + "version": "==1.3.7" + }, + "pluggy": { + "hashes": [ + "sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616", + "sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a" + ], + "version": "==0.8.1" + }, + "py": { + "hashes": [ + "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", + "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + ], + "version": "==1.7.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", + "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" + ], + "version": "==2.4.0" + }, + "pyflakes": { + "hashes": [ + "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", + "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" + ], + "version": "==2.0.0" + }, + "pytest": { + "hashes": [ + "sha256:41568ea7ecb4a68d7f63837cf65b92ce8d0105e43196ff2b26622995bb3dc4b2", + "sha256:c3c573a29d7c9547fb90217ece8a8843aa0c1328a797e200290dc3d0b4b823be" + ], + "index": "pypi", + "version": "==4.1.1" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + ], + "index": "pypi", + "version": "==2.21.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" + } + } +} diff --git a/README.md b/README.md index 7a93d88..31efe73 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ### Expansion modules -* [ASN History](misp_modules/modules/expansion/asn_history.py) - a hover and expansion module to expand an AS number with the ASN description and its history. +* [BGP Ranking](misp_modules/modules/expansion/bgpranking.py) - a hover and expansion module to expand an AS number with the ASN description, its history, and position in BGP Ranking. * [BTC transactions](misp_modules/modules/expansion/btc_steroids.py) - An expansion hover module to get a blockchain balance and the transactions from a BTC address in MISP. * [CIRCL Passive DNS](misp_modules/modules/expansion/circl_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [CIRCL Passive SSL](misp_modules/modules/expansion/circl_passivessl.py) - a hover and expansion module to expand IP addresses with the X.509 certificate seen. diff --git a/REQUIREMENTS b/REQUIREMENTS index 29387f1..709620a 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -1,31 +1,63 @@ -tornado -dnspython -requests -urlarchiver -passivetotal -PyPDNS -pypssl -redis -pyeupi -ipasn-redis -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 -wand -SPARQLWrapper -domaintools_api -pygeoip -bs4 -oauth2 -yara-python==3.8.0 -sigmatools -stix2-patterns -maclookup -vulners -psutil -blockchain -git+https://github.com/MISP/PyIntel471.git +-i https://pypi.org/simple +-e . +-e git+https://github.com/D4-project/BGP-Ranking.git/@7e698f87366e6f99b4d0d11852737db28e3ddc62#egg=pybgpranking&subdirectory=client +-e git+https://github.com/D4-project/IPASN-History.git/@e846cd36fe1ed6b22f60890bba89f84e61b62e59#egg=pyipasnhistory&subdirectory=client +-e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 +-e git+https://github.com/MISP/PyMISP.git@d4934cdf5f537c9f42ae37be7878de1848961de0#egg=pymisp +-e git+https://github.com/Rafiot/uwhoisd.git@f6f035e52213c8abc20f2084d28cfffb399457cb#egg=uwhois&subdirectory=client +-e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails +-e git+https://github.com/sebdraven/pyonyphe@66329baeee7cab844f2203c047c2551828eaf14d#egg=pyonyphe +aiohttp==3.4.4 +antlr4-python3-runtime==4.7.2 ; python_version >= '3' +async-timeout==3.0.1 +attrs==18.2.0 +beautifulsoup4==4.7.1 +blockchain==1.4.4 +certifi==2018.11.29 +chardet==3.0.4 +click-plugins==1.0.4 +click==7.0 +colorama==0.4.1 +dnspython==1.16.0 +domaintools-api==0.3.3 +enum-compat==0.0.2 +ez-setup==0.9 +future==0.17.1 +httplib2==0.12.0 +idna-ssl==1.1.0 ; python_version < '3.7' +idna==2.8 +isodate==0.6.0 +jsonschema==2.6.0 +maclookup==1.0.3 +multidict==4.5.2 +oauth2==1.9.0.post1 +passivetotal==1.0.30 +pillow==5.4.1 +psutil==5.4.8 +pyeupi==1.0 +pygeoip==0.3.2 +pyparsing==2.3.1 +pypdns==1.3 +pypssl==2.1 +pytesseract==0.2.6 +python-dateutil==2.7.5 +pyyaml==3.13 +rdflib==4.2.2 +redis==3.0.1 +requests-cache==0.4.13 +requests==2.21.0 +shodan==1.10.4 +sigmatools==0.7.1 +six==1.12.0 +soupsieve==1.7.2 +sparqlwrapper==1.8.2 +stix2-patterns==1.1.0 +tornado==5.1.1 +url-normalize==1.4.1 +urlarchiver==0.2 +urllib3==1.24.1 +vulners==1.3.6 +wand==0.5.0 +xlsxwriter==1.1.2 +yara-python==3.8.1 +yarl==1.3.0 diff --git a/doc/documentation.md b/doc/documentation.md index 49fb6b5..df5ce6e 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -2,25 +2,21 @@ ## Expansion Modules -#### [asn_history](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/asn_history.py) +#### [bgpranking](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/bgpranking.py) -Query an ASN description history service (https://github.com/CIRCL/ASN-Description-History.git). +Query BGP Ranking (https://bgpranking-ng.circl.lu/). - **features**: ->The module takes an AS number attribute as input and displays its description and history. +>The module takes an AS number attribute as input and displays its description and history, and position in BGP Ranking. > ->For a proper working, a communication with a redis database is needed, thus 3 parameters are needed: ->- host, the address of the redis server ->- port, the port used by redis ->- db, the index of the database used > - **input**: >Autonomous system number. - **output**: ->Text containing a description of the ASN and its history. +>Text containing a description of the ASN, its history, and the position in BGP Ranking. - **references**: ->https://github.com/CIRCL/ASN-Description-History.git +>https://github.com/D4-project/BGP-Ranking/ - **requirements**: ->asnhistory python library +>pybgpranking python library ----- @@ -331,17 +327,17 @@ Module to access intelmqs eventdb. #### [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). +Module to query an IP ASN history service (https://github.com/D4-project/IPASN-History). - **features**: ->This module takes an IP address attribute as input and queries the CIRCL IP ASN service to get additional information about the input. +>This module takes an IP address attribute as input and queries the CIRCL IPASN service to get additional information about the input. - **input**: >An IP address MISP attribute. - **output**: ->Text describing additional information about the input after a query on the IP-ASN-history database. +>Text describing additional information about the input after a query on the IPASN-history database. - **references**: ->https://www.circl.lu/services/ip-asn-history/ +>https://github.com/D4-project/IPASN-History - **requirements**: ->ipasn_redis: Python library to access IP-ASN-history instance via redis, An IP-ASN-history instance information (host, port and database index) +>pyipasnhistory: Python library to access IPASN-history instance ----- diff --git a/doc/expansion/asn_history.json b/doc/expansion/asn_history.json deleted file mode 100644 index b3eea26..0000000 --- a/doc/expansion/asn_history.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "description": "Query an ASN description history service (https://github.com/CIRCL/ASN-Description-History.git).", - "requirements": ["asnhistory python library"], - "features": "The module takes an AS number attribute as input and displays its description and history.\n\nFor a proper working, a communication with a redis database is needed, thus 3 parameters are needed:\n- host, the address of the redis server\n- port, the port used by redis\n- db, the index of the database used\n", - "references": ["https://github.com/CIRCL/ASN-Description-History.git"], - "input": "Autonomous system number.", - "output": "Text containing a description of the ASN and its history." -} diff --git a/doc/expansion/bgpranking.json b/doc/expansion/bgpranking.json new file mode 100644 index 0000000..a98b780 --- /dev/null +++ b/doc/expansion/bgpranking.json @@ -0,0 +1,8 @@ +{ + "description": "Query BGP Ranking (https://bgpranking-ng.circl.lu/).", + "requirements": ["pybgpranking python library"], + "features": "The module takes an AS number attribute as input and displays its description and history, and position in BGP Ranking.\n\n", + "references": ["https://github.com/D4-project/BGP-Ranking/"], + "input": "Autonomous system number.", + "output": "Text containing a description of the ASN, its history, and the position in BGP Ranking." +} diff --git a/doc/expansion/ipasn.json b/doc/expansion/ipasn.json index aa9d0b1..68b10d1 100644 --- a/doc/expansion/ipasn.json +++ b/doc/expansion/ipasn.json @@ -1,8 +1,8 @@ { - "description": "Module to query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git).", - "requirements": ["ipasn_redis: Python library to access IP-ASN-history instance via redis", "An IP-ASN-history instance information (host, port and database index)"], + "description": "Module to query an IP ASN history service (https://github.com/D4-project/IPASN-History).", + "requirements": ["pyipasnhistory: Python library to access IPASN-history instance"], "input": "An IP address MISP attribute.", - "output": "Text describing additional information about the input after a query on the IP-ASN-history database.", - "references": ["https://www.circl.lu/services/ip-asn-history/"], - "features": "This module takes an IP address attribute as input and queries the CIRCL IP ASN service to get additional information about the input." + "output": "Text describing additional information about the input after a query on the IPASN-history database.", + "references": ["https://github.com/D4-project/IPASN-History"], + "features": "This module takes an IP address attribute as input and queries the CIRCL IPASN service to get additional information about the input." } diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py index 6be61de..980ddf6 100644 --- a/doc/generate_documentation.py +++ b/doc/generate_documentation.py @@ -4,9 +4,10 @@ import json module_types = ['expansion', 'export_mod', 'import_mod'] titles = ['Expansion Modules', 'Export Modules', 'Import Modules'] -markdown= ["# MISP modules documentation\n"] +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)) @@ -18,7 +19,7 @@ def generate_doc(root_path): 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: + with open(filename, 'rt') as f: definition = json.loads(f.read()) if 'logo' in definition: markdown.append('\n\n'.format(definition.pop('logo'))) @@ -32,6 +33,7 @@ def generate_doc(root_path): 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) diff --git a/misp_modules/helpers/cache.py b/misp_modules/helpers/cache.py index 77506fa..93a1c88 100644 --- a/misp_modules/helpers/cache.py +++ b/misp_modules/helpers/cache.py @@ -30,7 +30,7 @@ db = 5 def selftest(enable=True): if not enable: return False - r = redis.StrictRedis(host=hostname, port=port, db=db) + r = redis.Redis(host=hostname, port=port, db=db) try: r.ping() except Exception: @@ -40,11 +40,11 @@ def selftest(enable=True): def get(modulename=None, query=None, value=None, debug=False): if (modulename is None or query is None): return False - r = redis.StrictRedis(host=hostname, port=port, db=db) + r = redis.Redis(host=hostname, port=port, db=db, decode_responses=True) h = hashlib.sha1() h.update(query.encode('UTF-8')) hv = h.hexdigest() - key = "m:" + modulename + ":" + hv + key = "m:{}:{}".format(modulename, hv) if not r.exists(key): if debug: @@ -58,7 +58,7 @@ def get(modulename=None, query=None, value=None, debug=False): def flush(): - r = redis.StrictRedis(host=hostname, port=port, db=db) + r = redis.StrictRedis(host=hostname, port=port, db=db, decode_responses=True) returncode = r.flushdb() return returncode @@ -70,7 +70,7 @@ if __name__ == "__main__": else: print("Selftest ok") v = get(modulename="testmodule", query="abcdef", value="barfoo", debug=True) - if v == b'barfoo': + if v == 'barfoo': print("Cache ok") v = get(modulename="testmodule", query="abcdef") print(v) diff --git a/misp_modules/modules/expansion/asn_history.py b/misp_modules/modules/expansion/bgpranking.py similarity index 50% rename from misp_modules/modules/expansion/asn_history.py rename to misp_modules/modules/expansion/bgpranking.py index 5a2f53d..b01088d 100755 --- a/misp_modules/modules/expansion/asn_history.py +++ b/misp_modules/modules/expansion/bgpranking.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- import json -from asnhistory import ASNHistory +from datetime import date, timedelta +from pybgpranking import BGPRanking misperrors = {'error': 'Error'} mispattributes = {'input': ['AS'], 'output': ['freetext']} @@ -9,8 +10,6 @@ moduleinfo = {'version': '0.1', 'author': 'Raphaël Vinot', 'description': 'Query an ASN Description history service (https://github.com/CIRCL/ASN-Description-History.git)', 'module-type': ['expansion', 'hover']} -moduleconfig = ['host', 'port', 'db'] - def handler(q=False): if q is False: @@ -22,19 +21,11 @@ def handler(q=False): misperrors['error'] = "Unsupported attributes type" return misperrors - if not request.get('config') and not (request['config'].get('host') - and request['config'].get('port') - and request['config'].get('db')): - misperrors['error'] = 'ASN description history configuration is missing' - return misperrors - - asnhistory = ASNHistory(host=request['config'].get('host'), - port=request['config'].get('port'), db=request['config'].get('db')) - - values = ['{} {}'.format(date.isoformat(), description) for date, description in asnhistory.get_all_descriptions(toquery)] + bgpranking = BGPRanking() + values = bgpranking.query(toquery, date=(date.today() - timedelta(1)).isoformat()) if not values: - misperrors['error'] = 'Unable to find descriptions for this ASN' + misperrors['error'] = 'Unable to find the ASN in BGP Ranking' return misperrors return {'results': [{'types': mispattributes['output'], 'values': values}]} @@ -44,5 +35,4 @@ def introspection(): def version(): - moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/expansion/ipasn.py b/misp_modules/modules/expansion/ipasn.py index f47d780..8489aa0 100755 --- a/misp_modules/modules/expansion/ipasn.py +++ b/misp_modules/modules/expansion/ipasn.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import json -from ipasn_redis import IPASN +from pyipasnhistory import IPASNHistory misperrors = {'error': 'Error'} mispattributes = {'input': ['ip-src', 'ip-dst'], 'output': ['freetext']} @@ -9,8 +9,6 @@ moduleinfo = {'version': '0.1', 'author': 'Raphaël Vinot', 'description': 'Query an IP ASN history service (https://github.com/CIRCL/IP-ASN-history.git)', 'module-type': ['expansion', 'hover']} -moduleconfig = ['host', 'port', 'db'] - def handler(q=False): if q is False: @@ -24,18 +22,8 @@ def handler(q=False): misperrors['error'] = "Unsupported attributes type" return misperrors - if not request.get('config') and not (request['config'].get('host') - and request['config'].get('port') - and request['config'].get('db')): - misperrors['error'] = 'IP ASN history configuration is missing' - return misperrors - - ipasn = IPASN(host=request['config'].get('host'), - port=request['config'].get('port'), db=request['config'].get('db')) - - values = [] - for first_seen, last_seen, asn, block in ipasn.aggregate_history(toquery): - values.append('{} {} {} {}'.format(first_seen.decode(), last_seen.decode(), asn.decode(), block)) + ipasn = IPASNHistory() + values = ipasn.query(toquery) if not values: misperrors['error'] = 'Unable to find the history of this IP' @@ -48,5 +36,4 @@ def introspection(): def version(): - moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/setup.py b/setup.py index f6c3a64..fc78750 100644 --- a/setup.py +++ b/setup.py @@ -23,18 +23,7 @@ setup( ], install_requires=[ 'tornado', - 'dnspython3', - 'requests', - 'urlarchiver', - 'passivetotal', - 'PyPDNS', - 'pypssl', - 'redis', - 'pyeupi', - 'ipasn-redis', - 'asnhistory', - 'pillow', - 'pytesseract', - 'shodan', + 'psutil', + 'redis>=3' ], ) From d5ec09fe4ad4209c387b1b0da82a412ea83f7658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Mon, 21 Jan 2019 13:57:45 +0100 Subject: [PATCH 296/401] fix: Change module name --- 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 18fd78c..559e5aa 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,6 +1,6 @@ from . import _vmray # noqa -__all__ = ['vmray_submit', 'asn_history', 'circl_passivedns', 'circl_passivessl', +__all__ = ['vmray_submit', 'bgpranking', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'btc_steroids', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', From b791b177c3a3c0f69c6e2c606989aa24c0203c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Mon, 21 Jan 2019 14:06:38 +0100 Subject: [PATCH 297/401] fix: Change in the imports --- misp_modules/modules/expansion/sigma_syntax_validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/sigma_syntax_validator.py b/misp_modules/modules/expansion/sigma_syntax_validator.py index b5fc0cf..658b4d3 100644 --- a/misp_modules/modules/expansion/sigma_syntax_validator.py +++ b/misp_modules/modules/expansion/sigma_syntax_validator.py @@ -1,8 +1,8 @@ import json try: import yaml - from sigma.parser import SigmaParser - from sigma.config import SigmaConfiguration + from sigma.parser.rule import SigmaParser + from sigma.configuration import SigmaConfiguration except ImportError: print("sigma or yaml is missing, use 'pip3 install sigmatools' to install it.") From 0189a117a3c2839e73173e69f1e0659df1c4bd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Mon, 21 Jan 2019 14:14:19 +0100 Subject: [PATCH 298/401] fix: Change in the imports in other sigma module --- misp_modules/modules/expansion/sigma_queries.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/sigma_queries.py b/misp_modules/modules/expansion/sigma_queries.py index 37a8c14..7799f2a 100644 --- a/misp_modules/modules/expansion/sigma_queries.py +++ b/misp_modules/modules/expansion/sigma_queries.py @@ -2,9 +2,10 @@ import sys import io import json try: - from sigma.parser import SigmaCollectionParser - from sigma.config import SigmaConfiguration - from sigma.backends import getBackend, BackendOptions + from sigma.parser.collection import SigmaCollectionParser + from sigma.configuration import SigmaConfiguration + from sigma.backends.base import BackendOptions + from sigma.backends.discovery import getBackend except ImportError: print("sigma or yaml is missing, use 'pip3 install sigmatools' to install it.") From c52b95cdbe05744d3402550ff20f1ca4c8ba3ba9 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Thu, 24 Jan 2019 09:51:46 +0100 Subject: [PATCH 299/401] sometimes server doesn't return expected values. fixed. --- .../modules/expansion/btc_steroids.py | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index 725cdf9..7011eda 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -131,11 +131,23 @@ def handler(q=False): i = 0 while i < n_tx: if click is False: - req = requests.get(blockchain_all.format(btc, "&limit=5&offset={}".format(i))) + try: + req = requests.get(blockchain_all.format(btc, "&limit=5&offset={}".format(i))) + except Exception as e: + # Lazy retry - cries for a function + print(e) + time.sleep(3) + req = requests.get(blockchain_all.format(btc, "&limit=5&offset={}".format(i))) if n_tx > 5: n_tx = 5 else: - req = requests.get(blockchain_all.format(btc, "&limit=50&offset={}".format(i))) + try: + req = requests.get(blockchain_all.format(btc, "&limit=50&offset={}".format(i))) + except Exception as e: + # Lazy retry - cries for a function + print(e) + time.sleep(3) + req = requests.get(blockchain_all.format(btc, "&limit=50&offset={}".format(i))) jreq = req.json() if jreq['txs']: for transactions in jreq['txs']: @@ -143,7 +155,15 @@ def handler(q=False): sum_counter = 0 for tx in transactions['inputs']: script_old = tx['script'] - if tx['prev_out']['value'] != 0 and tx['prev_out']['addr'] == btc: + try: + addr_in = tx['prev_out']['addr'] + except KeyError: + addr_in = None + try: + prev_out = tx['prev_out']['value'] + except KeyError: + prev_out = None + if prev_out != 0 and addr_in == btc: datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time']))) value = float(tx['prev_out']['value'] / 100000000) u, e = convert(value, transactions['time']) @@ -158,12 +178,20 @@ def handler(q=False): mprint("\t\t\t\t\t----------------------------------------------") mprint("#" + str(n_tx - i) + "\t\t\t\t Sum:\t-{0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR\n".format(sum, u, e).rstrip('0')) for tx in transactions['out']: - if tx['value'] != 0 and tx['addr'] == btc: + try: + addr_out = tx['addr'] + except KeyError: + addr_out = None + try: + prev_out = tx['prev_out']['value'] + except KeyError: + prev_out = None + if prev_out != 0 and addr_out == btc: datetime = time.strftime("%d %b %Y %H:%M:%S %Z", time.localtime(int(transactions['time']))) value = float(tx['value'] / 100000000) u, e = convert(value, transactions['time']) mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t {0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0')) - # i += 1 + #i += 1 i += 1 r = { From 3d47eb74207585870c160d2269c4a123dddcfc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 25 Jan 2019 10:45:02 +0100 Subject: [PATCH 300/401] fix: make flake8 happy --- misp_modules/modules/expansion/btc_steroids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index 7011eda..430c67d 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -191,7 +191,7 @@ def handler(q=False): value = float(tx['value'] / 100000000) u, e = convert(value, transactions['time']) mprint("#" + str(n_tx - i) + "\t" + str(datetime) + "\t {0:10.8f} BTC {1:10.2f} USD\t{2:10.2f} EUR".format(value, u, e).rstrip('0')) - #i += 1 + # i += 1 i += 1 r = { From 7a7b7b109f93126f14ea8697b27d359e096aa057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Mon, 4 Feb 2019 10:29:23 +0100 Subject: [PATCH 301/401] chg: Bump dependencies --- Pipfile.lock | 79 ++++++++++++++++++++++++++++------------------------ REQUIREMENTS | 10 +++---- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 02a61d1..19f32f0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -281,17 +281,17 @@ }, "psutil": { "hashes": [ - "sha256:1c19957883e0b93d081d41687089ad630e370e26dc49fd9df6951d6c891c4736", - "sha256:1c71b9716790e202a00ab0931a6d1e25db1aa1198bcacaea2f5329f75d257fff", - "sha256:3b7a4daf4223dae171a67a89314ac5ca0738e94064a78d99cfd751c55d05f315", - "sha256:3e19be3441134445347af3767fa7770137d472a484070840eee6653b94ac5576", - "sha256:6e265c8f3da00b015d24b842bfeb111f856b13d24f2c57036582568dc650d6c3", - "sha256:809c9cef0402e3e48b5a1dddc390a8a6ff58b15362ea5714494073fa46c3d293", - "sha256:b4d1b735bf5b120813f4c89db8ac22d89162c558cbd7fdd298866125fe906219", - "sha256:bbffac64cfd01c6bcf90eb1bedc6c80501c4dae8aef4ad6d6dd49f8f05f6fc5a", - "sha256:bfcea4f189177b2d2ce4a34b03c4ac32c5b4c22e21f5b093d9d315e6e253cd81" + "sha256:04d2071100aaad59f9bcbb801be2125d53b2e03b1517d9fed90b45eea51d297e", + "sha256:1aba93430050270750d046a179c5f3d6e1f5f8b96c20399ba38c596b28fc4d37", + "sha256:3ac48568f5b85fee44cd8002a15a7733deca056a191d313dbf24c11519c0c4a8", + "sha256:96f3fdb4ef7467854d46ad5a7e28eb4c6dc6d455d751ddf9640cd6d52bdb03d7", + "sha256:b755be689d6fc8ebc401e1d5ce5bac867e35788f10229e166338484eead51b12", + "sha256:c8ee08ad1b716911c86f12dc753eb1879006224fd51509f077987bb6493be615", + "sha256:d0c4230d60376aee0757d934020b14899f6020cd70ef8d2cb4f228b6ffc43e8f", + "sha256:d23f7025bac9b3e38adc6bd032cdaac648ac0074d18e36950a04af35458342e8", + "sha256:f0fcb7d3006dd4d9ccf3ccd0595d44c6abbfd433ec31b6ca177300ee3f19e54e" ], - "version": "==5.4.8" + "version": "==5.5.0" }, "pybgpranking": { "editable": true, @@ -333,7 +333,7 @@ "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "d4934cdf5f537c9f42ae37be7878de1848961de0" + "ref": "2c877f2aec11b7f5d2f23dfc5ce7398b2ce33b48" }, "pyonyphe": { "editable": true, @@ -400,10 +400,10 @@ }, "redis": { "hashes": [ - "sha256:2100750629beff143b6a200a2ea8e719fcf26420adabb81402895e144c5083cf", - "sha256:8e0bdd2de02e829b6225b25646f9fb9daffea99a252610d040409a6738541f0a" + "sha256:74c892041cba46078ae1ef845241548baa3bd3634f9a6f0f952f006eb1619c71", + "sha256:7ba8612bbfd966dea8c62322543fed0095da2834dbd5a7c124afbc617a156aa7" ], - "version": "==3.0.1" + "version": "==3.1.0" }, "requests": { "hashes": [ @@ -443,10 +443,10 @@ }, "soupsieve": { "hashes": [ - "sha256:10687fc53eeb3518e01a0ac84d3d711da623d3298a3039459d3f649927c4a270", - "sha256:b23a0d7da0247200fe83c67c34de9d7599ad404106367313d8e65e04174d0b4b" + "sha256:466910df7561796a60748826781ebe9a888f7a1668a636ae86783f44d10aae73", + "sha256:87db12ae79194f0ff9808d2b1641c4f031ae39ffa3cab6b907ea7c1e5e5ed445" ], - "version": "==1.7.2" + "version": "==1.7.3" }, "sparqlwrapper": { "hashes": [ @@ -505,12 +505,12 @@ }, "vulners": { "hashes": [ - "sha256:8b468db8f8b0bad39ae51ebd4247f6ead90b6f53699e03b91ff9d63da70554d7", - "sha256:ad72378c842096cad9ebf83aa53d330117ece5d208ed7c419a21c70a8d5e2236", - "sha256:ffc92a099eeddea840fd199665992c0eb6d7ad69ac3a6730a286d00600bc5f2c" + "sha256:5f05404041cfaa8e5367bf884fc9ee319ebf34bedc495d7f84c433fa121cdb49", + "sha256:919b24df64ea55b6a8ba13e2a0530578f8a4be6a9cee257bf2214046e81c6f35", + "sha256:d45ecb13f5111947056a2dcc071b3e3fd45f6ad654eda06526245bba3850325e" ], "index": "pypi", - "version": "==1.3.6" + "version": "==1.4.0" }, "wand": { "hashes": [ @@ -564,10 +564,10 @@ "develop": { "atomicwrites": { "hashes": [ - "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", - "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" ], - "version": "==1.2.1" + "version": "==1.3.0" }, "attrs": { "hashes": [ @@ -634,13 +634,20 @@ ], "version": "==4.5.2" }, + "entrypoints": { + "hashes": [ + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + ], + "version": "==0.3" + }, "flake8": { "hashes": [ - "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", - "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" + "sha256:09b9bb539920776da542e67a570a5df96ff933c9a08b62cfae920bcc789e4383", + "sha256:e0f8cd519cfc0072c0ee31add5def09d2b3ef6040b34dc426445c3af9b02163c" ], "index": "pypi", - "version": "==3.6.0" + "version": "==3.7.4" }, "idna": { "hashes": [ @@ -689,25 +696,25 @@ }, "pycodestyle": { "hashes": [ - "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", - "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" + "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", + "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" ], - "version": "==2.4.0" + "version": "==2.5.0" }, "pyflakes": { "hashes": [ - "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", - "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" + "sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d", + "sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "pytest": { "hashes": [ - "sha256:41568ea7ecb4a68d7f63837cf65b92ce8d0105e43196ff2b26622995bb3dc4b2", - "sha256:c3c573a29d7c9547fb90217ece8a8843aa0c1328a797e200290dc3d0b4b823be" + "sha256:65aeaa77ae87c7fc95de56285282546cfa9c886dc8e5dc78313db1c25e21bc07", + "sha256:6ac6d467d9f053e95aaacd79f831dbecfe730f419c6c7022cb316b365cd9199d" ], "index": "pypi", - "version": "==4.1.1" + "version": "==4.2.0" }, "requests": { "hashes": [ diff --git a/REQUIREMENTS b/REQUIREMENTS index 709620a..c3c16e6 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -3,7 +3,7 @@ -e git+https://github.com/D4-project/BGP-Ranking.git/@7e698f87366e6f99b4d0d11852737db28e3ddc62#egg=pybgpranking&subdirectory=client -e git+https://github.com/D4-project/IPASN-History.git/@e846cd36fe1ed6b22f60890bba89f84e61b62e59#egg=pyipasnhistory&subdirectory=client -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 --e git+https://github.com/MISP/PyMISP.git@d4934cdf5f537c9f42ae37be7878de1848961de0#egg=pymisp +-e git+https://github.com/MISP/PyMISP.git@2c877f2aec11b7f5d2f23dfc5ce7398b2ce33b48#egg=pymisp -e git+https://github.com/Rafiot/uwhoisd.git@f6f035e52213c8abc20f2084d28cfffb399457cb#egg=uwhois&subdirectory=client -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails -e git+https://github.com/sebdraven/pyonyphe@66329baeee7cab844f2203c047c2551828eaf14d#egg=pyonyphe @@ -33,7 +33,7 @@ multidict==4.5.2 oauth2==1.9.0.post1 passivetotal==1.0.30 pillow==5.4.1 -psutil==5.4.8 +psutil==5.5.0 pyeupi==1.0 pygeoip==0.3.2 pyparsing==2.3.1 @@ -43,20 +43,20 @@ pytesseract==0.2.6 python-dateutil==2.7.5 pyyaml==3.13 rdflib==4.2.2 -redis==3.0.1 +redis==3.1.0 requests-cache==0.4.13 requests==2.21.0 shodan==1.10.4 sigmatools==0.7.1 six==1.12.0 -soupsieve==1.7.2 +soupsieve==1.7.3 sparqlwrapper==1.8.2 stix2-patterns==1.1.0 tornado==5.1.1 url-normalize==1.4.1 urlarchiver==0.2 urllib3==1.24.1 -vulners==1.3.6 +vulners==1.4.0 wand==0.5.0 xlsxwriter==1.1.2 yara-python==3.8.1 From 454c9e0f437442d6cb3da71ca589df3c3c6f2a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Mon, 4 Feb 2019 11:05:51 +0100 Subject: [PATCH 302/401] fix: Pep8 related fixes. --- .../modules/expansion/circl_passivedns.py | 2 +- .../modules/expansion/xforceexchange.py | 207 +++++++++--------- misp_modules/modules/export_mod/liteexport.py | 112 +++++----- .../modules/export_mod/nexthinkexport.py | 2 +- .../modules/export_mod/osqueryexport.py | 2 +- misp_modules/modules/export_mod/pdfexport.py | 51 ++--- .../modules/import_mod/openiocimport.py | 2 +- .../import_mod/threatanalyzer_import.py | 4 +- 8 files changed, 192 insertions(+), 190 deletions(-) diff --git a/misp_modules/modules/expansion/circl_passivedns.py b/misp_modules/modules/expansion/circl_passivedns.py index 3da5bac..263b92a 100755 --- a/misp_modules/modules/expansion/circl_passivedns.py +++ b/misp_modules/modules/expansion/circl_passivedns.py @@ -32,7 +32,7 @@ def handler(q=False): res = x.query(toquery) out = '' for v in res: - out = out + "{} ".format(v['rdata']) + out = out + "{} ".format(v['rdata']) r = {'results': [{'types': mispattributes['output'], 'values': out}]} return r diff --git a/misp_modules/modules/expansion/xforceexchange.py b/misp_modules/modules/expansion/xforceexchange.py index 0f01e44..6bb7126 100644 --- a/misp_modules/modules/expansion/xforceexchange.py +++ b/misp_modules/modules/expansion/xforceexchange.py @@ -1,103 +1,104 @@ -import requests -import json -import sys - -BASEurl = "https://api.xforce.ibmcloud.com/" - -extensions = {"ip1": "ipr/%s", - "ip2": "ipr/malware/%s", - "url": "url/%s", - "hash": "malware/%s", - "vuln": "/vulnerabilities/search/%s", - "dns": "resolve/%s"} - -sys.path.append('./') - -misperrors = {'error': 'Error'} -mispattributes = {'input': ['ip-src', 'ip-dst', 'vulnerability', 'md5', 'sha1', 'sha256'], - 'output': ['ip-src', 'ip-dst', 'text', 'domain']} - -# possible module-types: 'expansion', 'hover' or both -moduleinfo = {'version': '1', 'author': 'Joerg Stephan (@johest)', - 'description': 'IBM X-Force Exchange expansion module', - 'module-type': ['expansion', 'hover']} - -# config fields that your code expects from the site admin -moduleconfig = ["apikey", "event_limit"] -limit = 5000 # Default - - -def MyHeader(key=False): - global limit - if key is False: - return None - - return {"Authorization": "Basic %s " % key, - "Accept": "application/json", - 'User-Agent': 'Mozilla 5.0'} - - -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"] += apicall("dns", q["ip-src"], key) - if "ip-dst" in q: - r["results"] += apicall("dns", q["ip-dst"], key) - if "md5" in q: - r["results"] += apicall("hash", q["md5"], key) - if "sha1" in q: - r["results"] += apicall("hash", q["sha1"], key) - if "sha256" in q: - r["results"] += apicall("hash", q["sha256"], key) - if 'vulnerability' in q: - r["results"] += apicall("vuln", q["vulnerability"], key) - if "domain" in q: - r["results"] += apicall("dns", q["domain"], key) - - uniq = [] - for res in r["results"]: - if res not in uniq: - uniq.append(res) - r["results"] = uniq - return r - - -def apicall(indicator_type, indicator, key=False): - try: - myURL = BASEurl + (extensions[str(indicator_type)]) % indicator - jsondata = requests.get(myURL, headers=MyHeader(key)).json() - except Exception: - jsondata = None - redata = [] - # print(jsondata) - if jsondata is not None: - if indicator_type is "hash": - if "malware" in jsondata: - lopointer = jsondata["malware"] - redata.append({"type": "text", "values": lopointer["risk"]}) - if indicator_type is "dns": - if "records" in str(jsondata): - lopointer = jsondata["Passive"]["records"] - for dataset in lopointer: - redata.append({"type": "domain", "values": dataset["value"]}) - - return redata - - -def introspection(): - return mispattributes - - -def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo +import requests +import json +import sys + +BASEurl = "https://api.xforce.ibmcloud.com/" + +extensions = {"ip1": "ipr/%s", + "ip2": "ipr/malware/%s", + "url": "url/%s", + "hash": "malware/%s", + "vuln": "/vulnerabilities/search/%s", + "dns": "resolve/%s"} + +sys.path.append('./') + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['ip-src', 'ip-dst', 'vulnerability', 'md5', 'sha1', 'sha256'], + 'output': ['ip-src', 'ip-dst', 'text', 'domain']} + +# possible module-types: 'expansion', 'hover' or both +moduleinfo = {'version': '1', 'author': 'Joerg Stephan (@johest)', + 'description': 'IBM X-Force Exchange expansion module', + 'module-type': ['expansion', 'hover']} + +# config fields that your code expects from the site admin +moduleconfig = ["apikey", "event_limit"] +limit = 5000 # Default + + +def MyHeader(key=False): + global limit + if key is False: + return None + + return {"Authorization": "Basic %s " % key, + "Accept": "application/json", + 'User-Agent': 'Mozilla 5.0'} + + +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"] += apicall("dns", q["ip-src"], key) + if "ip-dst" in q: + r["results"] += apicall("dns", q["ip-dst"], key) + if "md5" in q: + r["results"] += apicall("hash", q["md5"], key) + if "sha1" in q: + r["results"] += apicall("hash", q["sha1"], key) + if "sha256" in q: + r["results"] += apicall("hash", q["sha256"], key) + if 'vulnerability' in q: + r["results"] += apicall("vuln", q["vulnerability"], key) + if "domain" in q: + r["results"] += apicall("dns", q["domain"], key) + + uniq = [] + for res in r["results"]: + if res not in uniq: + uniq.append(res) + r["results"] = uniq + return r + + +def apicall(indicator_type, indicator, key=False): + try: + myURL = BASEurl + (extensions[str(indicator_type)]) % indicator + jsondata = requests.get(myURL, headers=MyHeader(key)).json() + except Exception: + jsondata = None + redata = [] + # print(jsondata) + if jsondata is not None: + if indicator_type == "hash": + if "malware" in jsondata: + lopointer = jsondata["malware"] + redata.append({"type": "text", "values": lopointer["risk"]}) + if indicator_type == "dns": + if "records" in str(jsondata): + lopointer = jsondata["Passive"]["records"] + for dataset in lopointer: + redata.append( + {"type": "domain", "values": dataset["value"]}) + + return redata + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/export_mod/liteexport.py b/misp_modules/modules/export_mod/liteexport.py index e89c1c1..870f52a 100755 --- a/misp_modules/modules/export_mod/liteexport.py +++ b/misp_modules/modules/export_mod/liteexport.py @@ -16,73 +16,73 @@ responseType = "application/json" def handler(q=False): - if q is False: - return False + if q is False: + return False - request = json.loads(q) + request = json.loads(q) - config = {} - if "config" in request: - config = request["config"] - else: - config = {"indent_json_export": None} + config = {} + if "config" in request: + config = request["config"] + else: + config = {"indent_json_export": None} - if config['indent_json_export'] is not None: - try: - config['indent_json_export'] = int(config['indent_json_export']) - except Exception: - config['indent_json_export'] = None + if config['indent_json_export'] is not None: + try: + config['indent_json_export'] = int(config['indent_json_export']) + except Exception: + config['indent_json_export'] = None - if 'data' not in request: - return False + if 'data' not in request: + return False - # ~ Misp json structur - liteEvent = {'Event': {}} + # ~ Misp json structur + liteEvent = {'Event': {}} - for evt in request['data']: - rawEvent = evt['Event'] - liteEvent['Event']['info'] = rawEvent['info'] - liteEvent['Event']['Attribute'] = [] + for evt in request['data']: + rawEvent = evt['Event'] + liteEvent['Event']['info'] = rawEvent['info'] + liteEvent['Event']['Attribute'] = [] - attrs = evt['Attribute'] - for attr in attrs: - if 'Internal reference' not in attr['category']: - liteAttr = {} - liteAttr['category'] = attr['category'] - liteAttr['type'] = attr['type'] - liteAttr['value'] = attr['value'] - liteEvent['Event']['Attribute'].append(liteAttr) + attrs = evt['Attribute'] + for attr in attrs: + if 'Internal reference' not in attr['category']: + liteAttr = {} + liteAttr['category'] = attr['category'] + liteAttr['type'] = attr['type'] + liteAttr['value'] = attr['value'] + liteEvent['Event']['Attribute'].append(liteAttr) - return {'response': [], - 'data': str(base64.b64encode(bytes( - json.dumps(liteEvent, indent=config['indent_json_export']), 'utf-8')), 'utf-8')} + return {'response': [], + 'data': str(base64.b64encode(bytes( + json.dumps(liteEvent, indent=config['indent_json_export']), 'utf-8')), 'utf-8')} def introspection(): - modulesetup = {} - try: - responseType - modulesetup['responseType'] = responseType - except NameError: - pass - try: - userConfig - modulesetup['userConfig'] = userConfig - except NameError: - pass - try: - outputFileExtension - modulesetup['outputFileExtension'] = outputFileExtension - except NameError: - pass - try: - inputSource - modulesetup['inputSource'] = inputSource - except NameError: - pass - return modulesetup + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + outputFileExtension + modulesetup['outputFileExtension'] = outputFileExtension + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/export_mod/nexthinkexport.py b/misp_modules/modules/export_mod/nexthinkexport.py index f1a0d79..c87b3fb 100755 --- a/misp_modules/modules/export_mod/nexthinkexport.py +++ b/misp_modules/modules/export_mod/nexthinkexport.py @@ -86,7 +86,7 @@ def handler(q=False): for event in request["data"]: for attribute in event["Attribute"]: if attribute['type'] in types_to_use: - output = output + handlers[attribute['type']](attribute['value'], config['Period']) + '\n' + output = output + handlers[attribute['type']](attribute['value'], config['Period']) + '\n' r = {"response": [], "data": str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} return r diff --git a/misp_modules/modules/export_mod/osqueryexport.py b/misp_modules/modules/export_mod/osqueryexport.py index ba98fe6..6368875 100755 --- a/misp_modules/modules/export_mod/osqueryexport.py +++ b/misp_modules/modules/export_mod/osqueryexport.py @@ -80,7 +80,7 @@ def handler(q=False): for event in request["data"]: for attribute in event["Attribute"]: if attribute['type'] in types_to_use: - output = output + handlers[attribute['type']](attribute['value']) + '\n' + output = output + handlers[attribute['type']](attribute['value']) + '\n' r = {"response": [], "data": str(base64.b64encode(bytes(output, 'utf-8')), 'utf-8')} return r diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 77a2e83..df7f879 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -152,36 +152,37 @@ def handler(q=False): command_line = 'asciidoctor-pdf -' args = shlex.split(command_line) with subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE) as process: - cmd_out, cmd_err = process.communicate(input=report.report.encode('utf-8')) + cmd_out, cmd_err = process.communicate( + input=report.report.encode('utf-8')) return {'response': [], 'data': str(base64.b64encode(cmd_out), 'utf-8')} def introspection(): - modulesetup = {} - try: - responseType - modulesetup['responseType'] = responseType - except NameError: - pass + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass - try: - userConfig - modulesetup['userConfig'] = userConfig - except NameError: - pass - try: - outputFileExtension - modulesetup['outputFileExtension'] = outputFileExtension - except NameError: - pass - try: - inputSource - modulesetup['inputSource'] = inputSource - except NameError: - pass - return modulesetup + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + outputFileExtension + modulesetup['outputFileExtension'] = outputFileExtension + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/import_mod/openiocimport.py b/misp_modules/modules/import_mod/openiocimport.py index c237bdc..074a464 100755 --- a/misp_modules/modules/import_mod/openiocimport.py +++ b/misp_modules/modules/import_mod/openiocimport.py @@ -63,7 +63,7 @@ def handler(q=False): "comment": getattr(attrib, 'comment', '')} # add tag if q.get('config') and q['config'].get('default tag') is not None: - toAppend["tags"] = q['config']['default tag'].split(",") + toAppend["tags"] = q['config']['default tag'].split(",") r["results"].append(toAppend) return r diff --git a/misp_modules/modules/import_mod/threatanalyzer_import.py b/misp_modules/modules/import_mod/threatanalyzer_import.py index 4ae1cd2..ff0a5b1 100755 --- a/misp_modules/modules/import_mod/threatanalyzer_import.py +++ b/misp_modules/modules/import_mod/threatanalyzer_import.py @@ -325,7 +325,7 @@ def process_analysis_json(analysis_json): for stored_created_file in process['stored_files']['stored_created_file']: stored_created_file['@filename'] = cleanup_filepath(stored_created_file['@filename']) if stored_created_file['@filename']: - if stored_created_file['@filesize'] is not '0': + if stored_created_file['@filesize'] != '0': val = '{}|{}'.format(stored_created_file['@filename'], stored_created_file['@md5']) # print("stored_created_file filename|md5: {}|{} IDS:yes".format( # stored_created_file['@filename'], # filename @@ -346,7 +346,7 @@ def process_analysis_json(analysis_json): for stored_modified_file in process['stored_files']['stored_modified_file']: stored_modified_file['@filename'] = cleanup_filepath(stored_modified_file['@filename']) if stored_modified_file['@filename']: - if stored_modified_file['@filesize'] is not '0': + if stored_modified_file['@filesize'] != '0': val = '{}|{}'.format(stored_modified_file['@filename'], stored_modified_file['@md5']) # print("stored_modified_file MODIFY FILE: {}\t{}".format( # stored_modified_file['@filename'], # filename From d1000d82c4d14f50c68a606cec897849281bf2e0 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 5 Feb 2019 14:46:42 +0100 Subject: [PATCH 303/401] add: New module to check if a bitcoin address has been abused - Also related update of documentation --- README.md | 1 + doc/README.md | 1262 ++++++++++++++++- doc/documentation.md | 1243 ---------------- doc/expansion/btc_scam_check.json | 9 + doc/expansion/{btc.json => btc_steroids.json} | 0 doc/generate_documentation.py | 2 +- misp_modules/modules/expansion/__init__.py | 2 +- .../modules/expansion/btc_scam_check.py | 43 + 8 files changed, 1316 insertions(+), 1246 deletions(-) mode change 120000 => 100644 doc/README.md delete mode 100644 doc/documentation.md create mode 100644 doc/expansion/btc_scam_check.json rename doc/expansion/{btc.json => btc_steroids.json} (100%) create mode 100644 misp_modules/modules/expansion/btc_scam_check.py diff --git a/README.md b/README.md index 368ef6f..e8fa0d2 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ### Expansion modules * [BGP Ranking](misp_modules/modules/expansion/bgpranking.py) - a hover and expansion module to expand an AS number with the ASN description, its history, and position in BGP Ranking. +* [BTC scam check](misp_modules/modules/expansion/btc_scam_check.py) - An expansion hover module to instantly check if a BTC address has been abused. * [BTC transactions](misp_modules/modules/expansion/btc_steroids.py) - An expansion hover module to get a blockchain balance and the transactions from a BTC address in MISP. * [CIRCL Passive DNS](misp_modules/modules/expansion/circl_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [CIRCL Passive SSL](misp_modules/modules/expansion/circl_passivessl.py) - a hover and expansion module to expand IP addresses with the X.509 certificate seen. diff --git a/doc/README.md b/doc/README.md deleted file mode 120000 index 0963ae8..0000000 --- a/doc/README.md +++ /dev/null @@ -1 +0,0 @@ -documentation.md \ No newline at end of file diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..e47470d --- /dev/null +++ b/doc/README.md @@ -0,0 +1,1261 @@ +# MISP modules documentation + +## Expansion Modules + +#### [bgpranking](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/bgpranking.py) + +Query BGP Ranking (https://bgpranking-ng.circl.lu/). +- **features**: +>The module takes an AS number attribute as input and displays its description and history, and position in BGP Ranking. +> +> +- **input**: +>Autonomous system number. +- **output**: +>Text containing a description of the ASN, its history, and the position in BGP Ranking. +- **references**: +>https://github.com/D4-project/BGP-Ranking/ +- **requirements**: +>pybgpranking python library + +----- + +#### [btc_scam_check](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/btc_scam_check.py) + + + +An expansion hover module to query a special dns blacklist to check if a bitcoin address has been abused. +- **features**: +>The module queries a dns blacklist directly with the bitcoin address and get a response if the address has been abused. +- **input**: +>btc address attribute. +- **output**: +>Text to indicate if the BTC address has been abused. +- **references**: +>https://btcblack.it/ +- **requirements**: +>dnspython3: dns python library + +----- + +#### [btc_steroids](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/btc_steroids.py) + + + +An expansion hover module to get a blockchain balance from a BTC address in MISP. +- **input**: +>btc address attribute. +- **output**: +>Text to describe the blockchain balance and the transactions related to the btc address in input. + +----- + +#### [circl_passivedns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/circl_passivedns.py) + + + +Module to access CIRCL Passive DNS. +- **features**: +>This module takes a hostname, domain or ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive DNS REST API to get and display information about this input. +> +>To make it work a username and a password are thus required to authenticate to the CIRCL Passive DNS API. +- **input**: +>Hostname, domain, or ip-address attribute. +- **ouput**: +>Text describing passive DNS information related to the input attribute. +- **references**: +>https://www.circl.lu/services/passive-dns/, https://datatracker.ietf.org/doc/draft-dulaunoy-dnsop-passive-dns-cof/ +- **requirements**: +>pypdns: Passive DNS python library, A CIRCL passive DNS account with username & password + +----- + +#### [circl_passivessl](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/circl_passivessl.py) + + + +Modules to access CIRCL Passive SSL. +- **features**: +>This module takes an ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive SSL REST API to get and display information about this input. +> +>To make it work a username and a password are thus required to authenticate to the CIRCL Passive SSL API. +- **input**: +>Ip-address attribute. +- **output**: +>Text describing passive SSL information related to the input attribute. +- **references**: +>https://www.circl.lu/services/passive-ssl/ +- **requirements**: +>pypssl: Passive SSL python library, A CIRCL passive SSL account with username & password + +----- + +#### [countrycode](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/countrycode.py) + +Module to expand country codes. +- **features**: +>The module takes a domain or a hostname as input, and returns the country it belongs to. +> +>For non country domains, a list of the most common possible extensions is used. +- **input**: +>Hostname or domain attribute. +- **output**: +>Text with the country code the input belongs to. + +----- + +#### [crowdstrike_falcon](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/crowdstrike_falcon.py) + + + +Module to query Crowdstrike Falcon. +- **features**: +>This module takes a MISP attribute as input to query a CrowdStrike Falcon API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. +> +>Please note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported. +- **input**: +>A MISP attribute included in the following list: +>- domain +>- email-attachment +>- email-dst +>- email-reply-to +>- email-src +>- email-subject +>- filename +>- hostname +>- ip-src +>- ip-dst +>- md5 +>- mutex +>- regkey +>- sha1 +>- sha256 +>- uri +>- url +>- user-agent +>- whois-registrant-email +>- x509-fingerprint-md5 +- **output**: +>MISP attributes mapped after the CrowdStrike API has been queried, included in the following list: +>- hostname +>- email-src +>- email-subject +>- filename +>- md5 +>- sha1 +>- sha256 +>- ip-dst +>- ip-dst +>- mutex +>- regkey +>- url +>- user-agent +>- x509-fingerprint-md5 +- **references**: +>https://www.crowdstrike.com/products/crowdstrike-falcon-faq/ +- **requirements**: +>A CrowdStrike API access (API id & key) + +----- + +#### [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. +- **features**: +>The module takes a vulnerability attribute as input and queries the CIRCL CVE search API to get information about the vulnerability as it is described in the list of CVEs. +- **input**: +>Vulnerability attribute. +- **output**: +>Text giving information about the CVE related to the Vulnerability. +- **references**: +>https://cve.circl.lu/, https://cve.mitre.org/ + +----- + +#### [dbl_spamhaus](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/dbl_spamhaus.py) + + + +Module to check Spamhaus DBL for a domain name. +- **features**: +>This modules takes a domain or a hostname in input and queries the Domain Block List provided by Spamhaus to determine what kind of domain it is. +> +>DBL then returns a response code corresponding to a certain classification of the domain we display. If the queried domain is not in the list, it is also mentionned. +> +>Please note that composite MISP attributes containing domain or hostname are supported as well. +- **input**: +>Domain or hostname attribute. +- **output**: +>Information about the nature of the input. +- **references**: +>https://www.spamhaus.org/faq/section/Spamhaus%20DBL +- **requirements**: +>dnspython3: DNS python3 library + +----- + +#### [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 domain MISP attributes. +- **features**: +>The module takes a domain of hostname attribute as input, and tries to resolve it. If no error is encountered, the IP address that resolves the domain is returned, otherwise the origin of the error is displayed. +> +>The address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8). +> +>Please note that composite MISP attributes containing domain or hostname are supported as well. +- **input**: +>Domain or hostname attribute. +- **output**: +>IP address resolving the input. +- **requirements**: +>dnspython3: DNS python3 library + +----- + +#### [domaintools](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/domaintools.py) + + + +DomainTools MISP expansion module. +- **features**: +>This module takes a MISP attribute as input to query the Domaintools API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. +> +>Please note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported. +- **input**: +>A MISP attribute included in the following list: +>- domain +>- hostname +>- email-src +>- email-dst +>- target-email +>- whois-registrant-email +>- whois-registrant-name +>- whois-registrant-phone +>- ip-src +>- ip-dst +- **output**: +>MISP attributes mapped after the Domaintools API has been queried, included in the following list: +>- whois-registrant-email +>- whois-registrant-phone +>- whois-registrant-name +>- whois-registrar +>- whois-creation-date +>- text +>- domain +- **references**: +>https://www.domaintools.com/ +- **requirements**: +>Domaintools python library, A Domaintools API access (username & apikey) + +----- + +#### [eupi](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/eupi.py) + + + +A module to query the Phishing Initiative service (https://phishing-initiative.lu). +- **features**: +>This module takes a domain, hostname or url MISP attribute as input to query the Phishing Initiative API. The API returns then the result of the query with some information about the value queried. +> +>Please note that composite attributes containing domain or hostname are also supported. +- **input**: +>A domain, hostname or url MISP attribute. +- **output**: +>Text containing information about the input, resulting from the query on Phishing Initiative. +- **references**: +>https://phishing-initiative.eu/?lang=en +- **requirements**: +>pyeupi: eupi python library, An access to the Phishing Initiative API (apikey & url) + +----- + +#### [farsight_passivedns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/farsight_passivedns.py) + + + +Module to access Farsight DNSDB Passive DNS. +- **features**: +>This module takes a domain, hostname or IP address MISP attribute as input to query the Farsight Passive DNS API. The API returns then the result of the query with some information about the value queried. +- **input**: +>A domain, hostname or IP address MISP attribute. +- **output**: +>Text containing information about the input, resulting from the query on the Farsight Passive DNS API. +- **references**: +>https://www.farsightsecurity.com/ +- **requirements**: +>An access to the Farsight Passive DNS API (apikey) + +----- + +#### [geoip_country](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/geoip_country.py) + + + +Module to query a local copy of Maxmind's Geolite database. +- **features**: +>This module takes an IP address MISP attribute as input and queries a local copy of the Maxmind's Geolite database to get information about the location of this IP address. +> +>Please note that composite attributes domain|ip are also supported. +- **input**: +>An IP address MISP Attribute. +- **output**: +>Text containing information about the location of the IP address. +- **references**: +>https://www.maxmind.com/en/home +- **requirements**: +>A local copy of Maxmind's Geolite database + +----- + +#### [hashdd](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/hashdd.py) + +A hover module to check hashes against hashdd.com including NSLR dataset. +- **features**: +>This module takes a hash attribute as input to check its known level, using the hashdd API. This information is then displayed. +- **input**: +>A hash MISP attribute (md5). +- **output**: +>Text describing the known level of the hash in the hashdd databases. +- **references**: +>https://hashdd.com/ + +----- + +#### [intelmq_eventdb](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/intelmq_eventdb.py) + + + +Module to access intelmqs eventdb. +- **features**: +>/!\ EXPERIMENTAL MODULE, some features may not work /!\ +> +>This module takes a domain, hostname, IP address or Autonomous system MISP attribute as input to query the IntelMQ database. The result of the query gives then additional information about the input. +- **input**: +>A hostname, domain, IP address or AS attribute. +- **output**: +>Text giving information about the input using IntelMQ database. +- **references**: +>https://github.com/certtools/intelmq, https://intelmq.readthedocs.io/en/latest/Developers-Guide/ +- **requirements**: +>psycopg2: Python library to support PostgreSQL, An access to the IntelMQ database (username, password, hostname and database reference) + +----- + +#### [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/D4-project/IPASN-History). +- **features**: +>This module takes an IP address attribute as input and queries the CIRCL IPASN service to get additional information about the input. +- **input**: +>An IP address MISP attribute. +- **output**: +>Text describing additional information about the input after a query on the IPASN-history database. +- **references**: +>https://github.com/D4-project/IPASN-History +- **requirements**: +>pyipasnhistory: Python library to access IPASN-history instance + +----- + +#### [iprep](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/iprep.py) + +Module to query IPRep data for IP addresses. +- **features**: +>This module takes an IP address attribute as input and queries the database from packetmail.net to get some information about the reputation of the IP. +- **input**: +>An IP address MISP attribute. +- **output**: +>Text describing additional information about the input after a query on the IPRep API. +- **references**: +>https://github.com/mahesh557/packetmail +- **requirements**: +>An access to the packetmail API (apikey) + +----- + +#### [macaddress_io](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/macaddress_io.py) + + + +MISP hover module for macaddress.io +- **features**: +>This module takes a MAC address attribute as input and queries macaddress.io for additional information. +> +>This information contains data about: +>- MAC address details +>- Vendor details +>- Block details +- **input**: +>MAC address MISP attribute. +- **output**: +>Text containing information on the MAC address fetched from a query on macaddress.io. +- **references**: +>https://macaddress.io/, https://github.com/CodeLineFi/maclookup-python +- **requirements**: +>maclookup: macaddress.io python library, An access to the macaddress.io API (apikey) + +----- + +#### [onyphe](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/onyphe.py) + + + +Module to process a query on Onyphe. +- **features**: +>This module takes a domain, hostname, or IP address attribute as input in order to query the Onyphe API. Data fetched from the query is then parsed and MISP attributes are extracted. +- **input**: +>A domain, hostname or IP address MISP attribute. +- **output**: +>MISP attributes fetched from the Onyphe query. +- **references**: +>https://www.onyphe.io/, https://github.com/sebdraven/pyonyphe +- **requirements**: +>onyphe python library, An access to the Onyphe API (apikey) + +----- + +#### [onyphe_full](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/onyphe_full.py) + + + +Module to process a full query on Onyphe. +- **features**: +>This module takes a domain, hostname, or IP address attribute as input in order to query the Onyphe API. Data fetched from the query is then parsed and MISP attributes are extracted. +> +>The parsing is here more advanced than the one on onyphe module, and is returning more attributes, since more fields of the query result are watched and parsed. +- **input**: +>A domain, hostname or IP address MISP attribute. +- **output**: +>MISP attributes fetched from the Onyphe query. +- **references**: +>https://www.onyphe.io/, https://github.com/sebdraven/pyonyphe +- **requirements**: +>onyphe python library, An access to the Onyphe API (apikey) + +----- + +#### [otx](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/otx.py) + + + +Module to get information from AlienVault OTX. +- **features**: +>This module takes a MISP attribute as input to query the OTX Alienvault API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. +- **input**: +>A MISP attribute included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- md5 +>- sha1 +>- sha256 +>- sha512 +- **output**: +>MISP attributes mapped from the result of the query on OTX, included in the following list: +>- domain +>- ip-src +>- ip-dst +>- text +>- md5 +>- sha1 +>- sha256 +>- sha512 +>- email +- **references**: +>https://www.alienvault.com/open-threat-exchange +- **requirements**: +>An access to the OTX API (apikey) + +----- + +#### [passivetotal](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/passivetotal.py) + + + + +- **features**: +>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 +- **input**: +>A MISP attribute included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- x509-fingerprint-sha1 +>- email-src +>- email-dst +>- target-email +>- whois-registrant-email +>- whois-registrant-phone +>- text +>- whois-registrant-name +>- whois-registrar +>- whois-creation-date +- **output**: +>MISP attributes mapped from the result of the query on PassiveTotal, included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- x509-fingerprint-sha1 +>- email-src +>- email-dst +>- target-email +>- whois-registrant-email +>- whois-registrant-phone +>- text +>- whois-registrant-name +>- whois-registrar +>- whois-creation-date +>- md5 +>- sha1 +>- sha256 +>- link +- **references**: +>https://www.passivetotal.org/register +- **requirements**: +>Passivetotal python library, An access to the PassiveTotal API (apikey) + +----- + +#### [rbl](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/rbl.py) + +Module to check an IPv4 address against known RBLs. +- **features**: +>This module takes an IP address attribute as input and queries multiple know Real-time Blackhost Lists to check if they have already seen this IP address. +> +>We display then all the information we get from those different sources. +- **input**: +>IP address attribute. +- **output**: +>Text with additional data from Real-time Blackhost Lists about the IP address. +- **references**: +>[RBLs list](https://github.com/MISP/misp-modules/blob/8817de476572a10a9c9d03258ec81ca70f3d926d/misp_modules/modules/expansion/rbl.py#L20) +- **requirements**: +>dnspython3: DNS python3 library + +----- + +#### [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. +- **features**: +>The module takes an IP address as input and tries to find the hostname this IP address is resolved into. +> +>The address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8). +> +>Please note that composite MISP attributes containing IP addresses are supported as well. +- **input**: +>An IP address attribute. +- **output**: +>Hostname attribute the input is resolved into. +- **requirements**: +>DNS python library + +----- + +#### [securitytrails](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/securitytrails.py) + + + +An expansion modules for SecurityTrails. +- **features**: +>The module takes a domain, hostname or IP address attribute as input and queries the SecurityTrails API with it. +> +>Multiple parsing operations are then processed on the result of the query to extract a much information as possible. +> +>From this data extracted are then mapped MISP attributes. +- **input**: +>A domain, hostname or IP address attribute. +- **output**: +>MISP attributes resulting from the query on SecurityTrails API, included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- dns-soa-email +>- whois-registrant-email +>- whois-registrant-phone +>- whois-registrant-name +>- whois-registrar +>- whois-creation-date +>- domain +- **references**: +>https://securitytrails.com/ +- **requirements**: +>dnstrails python library, An access to the SecurityTrails API (apikey) + +----- + +#### [shodan](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/shodan.py) + + + +Module to query on Shodan. +- **features**: +>The module takes an IP address as input and queries the Shodan API to get some additional data about it. +- **input**: +>An IP address MISP attribute. +- **output**: +>Text with additional data about the input, resulting from the query on Shodan. +- **references**: +>https://www.shodan.io/ +- **requirements**: +>shodan python library, An access to the Shodan API (apikey) + +----- + +#### [sigma_queries](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/sigma_queries.py) + + + +An expansion hover module to display the result of sigma queries. +- **features**: +>This module takes a Sigma rule attribute as input and tries all the different queries available to convert it into different formats recognized by SIEMs. +- **input**: +>A Sigma attribute. +- **output**: +>Text displaying results of queries on the Sigma attribute. +- **references**: +>https://github.com/Neo23x0/sigma/wiki +- **requirements**: +>Sigma python library + +----- + +#### [sigma_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/sigma_syntax_validator.py) + + + +An expansion hover module to perform a syntax check on sigma rules. +- **features**: +>This module takes a Sigma rule attribute as input and performs a syntax check on it. +> +>It displays then that the rule is valid if it is the case, and the error related to the rule otherwise. +- **input**: +>A Sigma attribute. +- **output**: +>Text describing the validity of the Sigma rule. +- **references**: +>https://github.com/Neo23x0/sigma/wiki +- **requirements**: +>Sigma python library, Yaml python library + +----- + +#### [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. +- **features**: +>This module takes a link or url attribute as input and caches the related web page. It returns then a link of the cached page. +- **input**: +>A link or url attribute. +- **output**: +>A malware-sample attribute describing the cached page. +- **references**: +>https://github.com/adulau/url_archiver +- **requirements**: +>urlarchiver: python library to fetch and archive URL on the file-system + +----- + +#### [stix2_pattern_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py) + + + +An expansion hover module to perform a syntax check on stix2 patterns. +- **features**: +>This module takes a STIX2 pattern attribute as input and performs a syntax check on it. +> +>It displays then that the rule is valid if it is the case, and the error related to the rule otherwise. +- **input**: +>A STIX2 pattern attribute. +- **output**: +>Text describing the validity of the STIX2 pattern. +- **references**: +>[STIX2.0 patterning specifications](http://docs.oasis-open.org/cti/stix/v2.0/cs01/part5-stix-patterning/stix-v2.0-cs01-part5-stix-patterning.html) +- **requirements**: +>stix2patterns python library + +----- + +#### [threatcrowd](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/threatcrowd.py) + + + +Module to get information from ThreatCrowd. +- **features**: +>This module takes a MISP attribute as input and queries ThreatCrowd with it. +> +>The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. +- **input**: +>A MISP attribute included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- md5 +>- sha1 +>- sha256 +>- sha512 +>- whois-registrant-email +- **output**: +>MISP attributes mapped from the result of the query on ThreatCrowd, included in the following list: +>- domain +>- ip-src +>- ip-dst +>- text +>- md5 +>- sha1 +>- sha256 +>- sha512 +>- hostname +>- whois-registrant-email +- **references**: +>https://www.threatcrowd.org/ + +----- + +#### [threatminer](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/threatminer.py) + + + +Module to get information from ThreatMiner. +- **features**: +>This module takes a MISP attribute as input and queries ThreatMiner with it. +> +>The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. +- **input**: +>A MISP attribute included in the following list: +>- hostname +>- domain +>- ip-src +>- ip-dst +>- md5 +>- sha1 +>- sha256 +>- sha512 +- **output**: +>MISP attributes mapped from the result of the query on ThreatMiner, included in the following list: +>- domain +>- ip-src +>- ip-dst +>- text +>- md5 +>- sha1 +>- sha256 +>- sha512 +>- ssdeep +>- authentihash +>- filename +>- whois-registrant-email +>- url +>- link +- **references**: +>https://www.threatminer.org/ + +----- + +#### [urlscan](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/urlscan.py) + + + +An expansion module to query urlscan.io. +- **features**: +>This module takes a MISP attribute as input and queries urlscan.io with it. +> +>The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. +- **input**: +>A domain, hostname or url attribute. +- **output**: +>MISP attributes mapped from the result of the query on urlscan.io. +- **references**: +>https://urlscan.io/ +- **requirements**: +>An access to the urlscan.io API + +----- + +#### [virustotal](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/virustotal.py) + + + +Module to get information from virustotal. +- **features**: +>This module takes a MISP attribute as input and queries the VirusTotal API with it, in order to get additional data on the input attribute. +> +>Multiple recursive requests on the API can then be processed on some attributes found in the first request. A limit can be set to restrict the number of values to query again, and at the same time the number of request submitted to the API. +> +>This limit is important because the default user VirusTotal apikey only allows to process a certain nunmber of queries per minute. As a consequence it is recommended to have a larger number of requests or a private apikey. +> +>Data is then mapped into MISP attributes. +- **input**: +>A domain, hash (md5, sha1, sha256 or sha512), hostname or IP address attribute. +- **output**: +>MISP attributes mapped from the rersult of the query on VirusTotal API. +- **references**: +>https://www.virustotal.com/ +- **requirements**: +>An access to the VirusTotal API (apikey) + +----- + +#### [vmray_submit](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vmray_submit.py) + + + +Module to submit a sample to VMRay. +- **features**: +>This module takes an attachment or malware-sample attribute as input to query the VMRay API. +> +>The sample contained within the attribute in then enriched with data from VMRay mapped into MISP attributes. +- **input**: +>An attachment or malware-sample attribute. +- **output**: +>MISP attributes mapped from the result of the query on VMRay API, included in the following list: +>- text +>- sha1 +>- sha256 +>- md5 +>- link +- **references**: +>https://www.vmray.com/ +- **requirements**: +>An access to the VMRay API (apikey & url) + +----- + +#### [vulndb](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vulndb.py) + + + +Module to query VulnDB (RiskBasedSecurity.com). +- **features**: +>This module takes a vulnerability attribute as input and queries VulnDB in order to get some additional data about it. +> +>The API gives the result of the query which can be displayed in the screen, and/or mapped into MISP attributes to add in the event. +- **input**: +>A vulnerability attribute. +- **output**: +>Additional data enriching the CVE input, fetched from VulnDB. +- **references**: +>https://vulndb.cyberriskanalytics.com/ +- **requirements**: +>An access to the VulnDB API (apikey, apisecret) + +----- + +#### [vulners](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vulners.py) + + + +An expansion hover module to expand information about CVE id using Vulners API. +- **features**: +>This module takes a vulnerability attribute as input and queries the Vulners API in order to get some additional data about it. +> +>The API then returns details about the vulnerability. +- **input**: +>A vulnerability attribute. +- **output**: +>Text giving additional information about the CVE in input. +- **references**: +>https://vulners.com/ +- **requirements**: +>Vulners python library, An access to the Vulners API + +----- + +#### [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). +- **features**: +>This module takes a domain or IP address attribute as input and queries a 'Univseral Whois proxy server' to get the correct details of the Whois query on the input value (check the references for more details about this whois server). +- **input**: +>A domain or IP address attribute. +- **output**: +>Text describing the result of a whois request for the input value. +- **references**: +>https://github.com/rafiot/uwhoisd +- **requirements**: +>uwhois: A whois python library + +----- + +#### [wiki](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/wiki.py) + + + +An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis. +- **features**: +>This module takes a text attribute as input and queries the Wikidata API. If the text attribute is clear enough to define a specific term, the API returns a wikidata link in response. +- **input**: +>Text attribute. +- **output**: +>Text attribute. +- **references**: +>https://www.wikidata.org +- **requirements**: +>SPARQLWrapper python library + +----- + +#### [xforceexchange](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/xforceexchange.py) + + + +An expansion module for IBM X-Force Exchange. +- **features**: +>This module takes a MISP attribute as input to query the X-Force API. The API returns then additional information known in their threats data, that is mapped into MISP attributes. +- **input**: +>A MISP attribute included in the following list: +>- ip-src +>- ip-dst +>- vulnerability +>- md5 +>- sha1 +>- sha256 +- **output**: +>MISP attributes mapped from the result of the query on X-Force Exchange. +- **references**: +>https://exchange.xforce.ibmcloud.com/ +- **requirements**: +>An access to the X-Force API (apikey) + +----- + +#### [yara_query](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/yara_query.py) + + + +An expansion & hover module to translate any hash attribute into a yara rule. +- **features**: +>The module takes a hash attribute (md5, sha1, sha256, imphash) as input, and is returning a YARA rule from it. This YARA rule is also validated using the same method as in 'yara_syntax_validator' module. +>Both hover and expansion functionalities are supported with this module, where the hover part is displaying the resulting YARA rule and the expansion part allows you to add the rule as a new attribute, as usual with expansion modules. +- **input**: +>MISP Hash attribute (md5, sha1, sha256, imphash, or any of the composite attribute with filename and one of the previous hash type). +- **output**: +>YARA rule. +- **references**: +>https://virustotal.github.io/yara/, https://github.com/virustotal/yara-python +- **requirements**: +>yara-python python library + +----- + +#### [yara_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/yara_syntax_validator.py) + + + +An expansion hover module to perform a syntax check on if yara rules are valid or not. +- **features**: +>This modules simply takes a YARA rule as input, and checks its syntax. It returns then a confirmation if the syntax is valid, otherwise the syntax error is displayed. +- **input**: +>YARA rule attribute. +- **output**: +>Text to inform users if their rule is valid. +- **references**: +>http://virustotal.github.io/yara/ +- **requirements**: +>yara_python python library + +----- + +## Export Modules + +#### [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**: +>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. +- **input**: +>MISP Event attributes +- **output**: +>Common Event Format file +- **references**: +>https://community.softwaregrp.com/t5/ArcSight-Connectors/ArcSight-Common-Event-Format-CEF-Guide/ta-p/1589306?attachment-id=65537 + +----- + +#### [goamlexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/goamlexport.py) + + + +This module is used to export MISP events containing transaction objects into GoAML format. +- **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. +- **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). +- **references**: +>http://goaml.unodc.org/ +- **requirements**: +>PyMISP, MISP objects + +----- + +#### [liteexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/liteexport.py) + +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 + +----- + +#### [nexthinkexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/nexthinkexport.py) + + + +Nexthink NXQL query export module +- **features**: +>This module export an event as Nexthink NXQL queries that can then be used in your own python3 tool or from wget/powershell +- **input**: +>MISP Event attributes +- **output**: +>Nexthink NXQL queries +- **references**: +>https://doc.nexthink.com/Documentation/Nexthink/latest/APIAndIntegrations/IntroducingtheWebAPIV2 + +----- + +#### [osqueryexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/osqueryexport.py) + + + +OSQuery export of a MISP event. +- **features**: +>This module export an event as osquery queries that can be used in packs or in fleet management solution like Kolide. +- **input**: +>MISP Event attributes +- **output**: +>osquery SQL queries + +----- + +#### [pdfexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/pdfexport.py) + +Simple export of a MISP event to PDF. +- **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. +- **input**: +>MISP Event +- **output**: +>MISP Event in a PDF file. +- **references**: +>https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html +- **requirements**: +>PyMISP, asciidoctor + +----- + +#### [testexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/testexport.py) + +Skeleton export module. + +----- + +#### [threatStream_misp_export](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/threatStream_misp_export.py) + + + +Module to export a structured CSV file for uploading to threatStream. +- **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. +- **input**: +>MISP Event attributes +- **output**: +>ThreatStream CSV format file +- **references**: +>https://www.anomali.com/platform/threatstream, https://github.com/threatstream +- **requirements**: +>csv + +----- + +#### [threat_connect_export](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/threat_connect_export.py) + + + +Module to export a structured CSV file for uploading to ThreatConnect. +- **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. +- **input**: +>MISP Event attributes +- **output**: +>ThreatConnect CSV format file +- **references**: +>https://www.threatconnect.com +- **requirements**: +>csv + +----- + +## Import Modules + +#### [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. +- **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. +- **input**: +>CSV format file. +- **output**: +>MISP Event attributes +- **references**: +>https://tools.ietf.org/html/rfc4180, https://tools.ietf.org/html/rfc7111 +- **requirements**: +>PyMISP + +----- + +#### [cuckooimport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/cuckooimport.py) + + + +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. +- **input**: +>Cuckoo JSON file +- **output**: +>MISP Event attributes +- **references**: +>https://cuckoosandbox.org/, https://github.com/cuckoosandbox/cuckoo + +----- + +#### [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**: +>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](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/goamlimport.py) + + + +Module to import MISP objects about financial transactions from GoAML files. +- **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. +- **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. +- **references**: +>http://goaml.unodc.org/ +- **requirements**: +>PyMISP + +----- + +#### [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**: +>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 + +----- + +#### [ocr](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/ocr.py) + +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](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/openiocimport.py) + +Module to import OpenIOC packages. +- **features**: +>The module imports MISP Attributes from OpenIOC packages, there is then no special feature for users to make it work. +- **input**: +>OpenIOC packages +- **output**: +>MISP Event attributes +- **references**: +>https://www.fireeye.com/blog/threat-research/2013/10/openioc-basics.html +- **requirements**: +>PyMISP + +----- + +#### [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**: +>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. +- **input**: +>ThreatAnalyzer format file +- **output**: +>MISP Event attributes +- **references**: +>https://www.threattrack.com/malware-analysis.aspx + +----- + +#### [vmray_import](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/vmray_import.py) + + + +Module to import VMRay (VTI) results. +- **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. +- **input**: +>VMRay format +- **output**: +>MISP Event attributes +- **references**: +>https://www.vmray.com/ +- **requirements**: +>vmray_rest_api + +----- diff --git a/doc/documentation.md b/doc/documentation.md deleted file mode 100644 index 31f09ed..0000000 --- a/doc/documentation.md +++ /dev/null @@ -1,1243 +0,0 @@ -# MISP modules documentation - -## Expansion Modules - -#### [bgpranking](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/bgpranking.py) - -Query BGP Ranking (https://bgpranking-ng.circl.lu/). -- **features**: ->The module takes an AS number attribute as input and displays its description and history, and position in BGP Ranking. -> -> -- **input**: ->Autonomous system number. -- **output**: ->Text containing a description of the ASN, its history, and the position in BGP Ranking. -- **references**: ->https://github.com/D4-project/BGP-Ranking/ -- **requirements**: ->pybgpranking python library - ------ - -#### [btc](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/btc.py) - - - -An expansion hover module to get a blockchain balance from a BTC address in MISP. -- **input**: ->btc address attribute. -- **output**: ->Text to describe the blockchain balance and the transactions related to the btc address in input. - ------ - -#### [circl_passivedns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/circl_passivedns.py) - - - -Module to access CIRCL Passive DNS. -- **features**: ->This module takes a hostname, domain or ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive DNS REST API to get and display information about this input. -> ->To make it work a username and a password are thus required to authenticate to the CIRCL Passive DNS API. -- **input**: ->Hostname, domain, or ip-address attribute. -- **ouput**: ->Text describing passive DNS information related to the input attribute. -- **references**: ->https://www.circl.lu/services/passive-dns/, https://datatracker.ietf.org/doc/draft-dulaunoy-dnsop-passive-dns-cof/ -- **requirements**: ->pypdns: Passive DNS python library, A CIRCL passive DNS account with username & password - ------ - -#### [circl_passivessl](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/circl_passivessl.py) - - - -Modules to access CIRCL Passive SSL. -- **features**: ->This module takes an ip-address (ip-src or ip-dst) attribute as input, and queries the CIRCL Passive SSL REST API to get and display information about this input. -> ->To make it work a username and a password are thus required to authenticate to the CIRCL Passive SSL API. -- **input**: ->Ip-address attribute. -- **output**: ->Text describing passive SSL information related to the input attribute. -- **references**: ->https://www.circl.lu/services/passive-ssl/ -- **requirements**: ->pypssl: Passive SSL python library, A CIRCL passive SSL account with username & password - ------ - -#### [countrycode](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/countrycode.py) - -Module to expand country codes. -- **features**: ->The module takes a domain or a hostname as input, and returns the country it belongs to. -> ->For non country domains, a list of the most common possible extensions is used. -- **input**: ->Hostname or domain attribute. -- **output**: ->Text with the country code the input belongs to. - ------ - -#### [crowdstrike_falcon](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/crowdstrike_falcon.py) - - - -Module to query Crowdstrike Falcon. -- **features**: ->This module takes a MISP attribute as input to query a CrowdStrike Falcon API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. -> ->Please note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported. -- **input**: ->A MISP attribute included in the following list: ->- domain ->- email-attachment ->- email-dst ->- email-reply-to ->- email-src ->- email-subject ->- filename ->- hostname ->- ip-src ->- ip-dst ->- md5 ->- mutex ->- regkey ->- sha1 ->- sha256 ->- uri ->- url ->- user-agent ->- whois-registrant-email ->- x509-fingerprint-md5 -- **output**: ->MISP attributes mapped after the CrowdStrike API has been queried, included in the following list: ->- hostname ->- email-src ->- email-subject ->- filename ->- md5 ->- sha1 ->- sha256 ->- ip-dst ->- ip-dst ->- mutex ->- regkey ->- url ->- user-agent ->- x509-fingerprint-md5 -- **references**: ->https://www.crowdstrike.com/products/crowdstrike-falcon-faq/ -- **requirements**: ->A CrowdStrike API access (API id & key) - ------ - -#### [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. -- **features**: ->The module takes a vulnerability attribute as input and queries the CIRCL CVE search API to get information about the vulnerability as it is described in the list of CVEs. -- **input**: ->Vulnerability attribute. -- **output**: ->Text giving information about the CVE related to the Vulnerability. -- **references**: ->https://cve.circl.lu/, https://cve.mitre.org/ - ------ - -#### [dbl_spamhaus](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/dbl_spamhaus.py) - - - -Module to check Spamhaus DBL for a domain name. -- **features**: ->This modules takes a domain or a hostname in input and queries the Domain Block List provided by Spamhaus to determine what kind of domain it is. -> ->DBL then returns a response code corresponding to a certain classification of the domain we display. If the queried domain is not in the list, it is also mentionned. -> ->Please note that composite MISP attributes containing domain or hostname are supported as well. -- **input**: ->Domain or hostname attribute. -- **output**: ->Information about the nature of the input. -- **references**: ->https://www.spamhaus.org/faq/section/Spamhaus%20DBL -- **requirements**: ->dnspython3: DNS python3 library - ------ - -#### [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 domain MISP attributes. -- **features**: ->The module takes a domain of hostname attribute as input, and tries to resolve it. If no error is encountered, the IP address that resolves the domain is returned, otherwise the origin of the error is displayed. -> ->The address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8). -> ->Please note that composite MISP attributes containing domain or hostname are supported as well. -- **input**: ->Domain or hostname attribute. -- **output**: ->IP address resolving the input. -- **requirements**: ->dnspython3: DNS python3 library - ------ - -#### [domaintools](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/domaintools.py) - - - -DomainTools MISP expansion module. -- **features**: ->This module takes a MISP attribute as input to query the Domaintools API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. -> ->Please note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported. -- **input**: ->A MISP attribute included in the following list: ->- domain ->- hostname ->- email-src ->- email-dst ->- target-email ->- whois-registrant-email ->- whois-registrant-name ->- whois-registrant-phone ->- ip-src ->- ip-dst -- **output**: ->MISP attributes mapped after the Domaintools API has been queried, included in the following list: ->- whois-registrant-email ->- whois-registrant-phone ->- whois-registrant-name ->- whois-registrar ->- whois-creation-date ->- text ->- domain -- **references**: ->https://www.domaintools.com/ -- **requirements**: ->Domaintools python library, A Domaintools API access (username & apikey) - ------ - -#### [eupi](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/eupi.py) - - - -A module to query the Phishing Initiative service (https://phishing-initiative.lu). -- **features**: ->This module takes a domain, hostname or url MISP attribute as input to query the Phishing Initiative API. The API returns then the result of the query with some information about the value queried. -> ->Please note that composite attributes containing domain or hostname are also supported. -- **input**: ->A domain, hostname or url MISP attribute. -- **output**: ->Text containing information about the input, resulting from the query on Phishing Initiative. -- **references**: ->https://phishing-initiative.eu/?lang=en -- **requirements**: ->pyeupi: eupi python library, An access to the Phishing Initiative API (apikey & url) - ------ - -#### [farsight_passivedns](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/farsight_passivedns.py) - - - -Module to access Farsight DNSDB Passive DNS. -- **features**: ->This module takes a domain, hostname or IP address MISP attribute as input to query the Farsight Passive DNS API. The API returns then the result of the query with some information about the value queried. -- **input**: ->A domain, hostname or IP address MISP attribute. -- **output**: ->Text containing information about the input, resulting from the query on the Farsight Passive DNS API. -- **references**: ->https://www.farsightsecurity.com/ -- **requirements**: ->An access to the Farsight Passive DNS API (apikey) - ------ - -#### [geoip_country](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/geoip_country.py) - - - -Module to query a local copy of Maxmind's Geolite database. -- **features**: ->This module takes an IP address MISP attribute as input and queries a local copy of the Maxmind's Geolite database to get information about the location of this IP address. -> ->Please note that composite attributes domain|ip are also supported. -- **input**: ->An IP address MISP Attribute. -- **output**: ->Text containing information about the location of the IP address. -- **references**: ->https://www.maxmind.com/en/home -- **requirements**: ->A local copy of Maxmind's Geolite database - ------ - -#### [hashdd](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/hashdd.py) - -A hover module to check hashes against hashdd.com including NSLR dataset. -- **features**: ->This module takes a hash attribute as input to check its known level, using the hashdd API. This information is then displayed. -- **input**: ->A hash MISP attribute (md5). -- **output**: ->Text describing the known level of the hash in the hashdd databases. -- **references**: ->https://hashdd.com/ - ------ - -#### [intelmq_eventdb](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/intelmq_eventdb.py) - - - -Module to access intelmqs eventdb. -- **features**: ->/!\ EXPERIMENTAL MODULE, some features may not work /!\ -> ->This module takes a domain, hostname, IP address or Autonomous system MISP attribute as input to query the IntelMQ database. The result of the query gives then additional information about the input. -- **input**: ->A hostname, domain, IP address or AS attribute. -- **output**: ->Text giving information about the input using IntelMQ database. -- **references**: ->https://github.com/certtools/intelmq, https://intelmq.readthedocs.io/en/latest/Developers-Guide/ -- **requirements**: ->psycopg2: Python library to support PostgreSQL, An access to the IntelMQ database (username, password, hostname and database reference) - ------ - -#### [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/D4-project/IPASN-History). -- **features**: ->This module takes an IP address attribute as input and queries the CIRCL IPASN service to get additional information about the input. -- **input**: ->An IP address MISP attribute. -- **output**: ->Text describing additional information about the input after a query on the IPASN-history database. -- **references**: ->https://github.com/D4-project/IPASN-History -- **requirements**: ->pyipasnhistory: Python library to access IPASN-history instance - ------ - -#### [iprep](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/iprep.py) - -Module to query IPRep data for IP addresses. -- **features**: ->This module takes an IP address attribute as input and queries the database from packetmail.net to get some information about the reputation of the IP. -- **input**: ->An IP address MISP attribute. -- **output**: ->Text describing additional information about the input after a query on the IPRep API. -- **references**: ->https://github.com/mahesh557/packetmail -- **requirements**: ->An access to the packetmail API (apikey) - ------ - -#### [macaddress_io](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/macaddress_io.py) - - - -MISP hover module for macaddress.io -- **features**: ->This module takes a MAC address attribute as input and queries macaddress.io for additional information. -> ->This information contains data about: ->- MAC address details ->- Vendor details ->- Block details -- **input**: ->MAC address MISP attribute. -- **output**: ->Text containing information on the MAC address fetched from a query on macaddress.io. -- **references**: ->https://macaddress.io/, https://github.com/CodeLineFi/maclookup-python -- **requirements**: ->maclookup: macaddress.io python library, An access to the macaddress.io API (apikey) - ------ - -#### [onyphe](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/onyphe.py) - - - -Module to process a query on Onyphe. -- **features**: ->This module takes a domain, hostname, or IP address attribute as input in order to query the Onyphe API. Data fetched from the query is then parsed and MISP attributes are extracted. -- **input**: ->A domain, hostname or IP address MISP attribute. -- **output**: ->MISP attributes fetched from the Onyphe query. -- **references**: ->https://www.onyphe.io/, https://github.com/sebdraven/pyonyphe -- **requirements**: ->onyphe python library, An access to the Onyphe API (apikey) - ------ - -#### [onyphe_full](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/onyphe_full.py) - - - -Module to process a full query on Onyphe. -- **features**: ->This module takes a domain, hostname, or IP address attribute as input in order to query the Onyphe API. Data fetched from the query is then parsed and MISP attributes are extracted. -> ->The parsing is here more advanced than the one on onyphe module, and is returning more attributes, since more fields of the query result are watched and parsed. -- **input**: ->A domain, hostname or IP address MISP attribute. -- **output**: ->MISP attributes fetched from the Onyphe query. -- **references**: ->https://www.onyphe.io/, https://github.com/sebdraven/pyonyphe -- **requirements**: ->onyphe python library, An access to the Onyphe API (apikey) - ------ - -#### [otx](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/otx.py) - - - -Module to get information from AlienVault OTX. -- **features**: ->This module takes a MISP attribute as input to query the OTX Alienvault API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes. -- **input**: ->A MISP attribute included in the following list: ->- hostname ->- domain ->- ip-src ->- ip-dst ->- md5 ->- sha1 ->- sha256 ->- sha512 -- **output**: ->MISP attributes mapped from the result of the query on OTX, included in the following list: ->- domain ->- ip-src ->- ip-dst ->- text ->- md5 ->- sha1 ->- sha256 ->- sha512 ->- email -- **references**: ->https://www.alienvault.com/open-threat-exchange -- **requirements**: ->An access to the OTX API (apikey) - ------ - -#### [passivetotal](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/passivetotal.py) - - - - -- **features**: ->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 -- **input**: ->A MISP attribute included in the following list: ->- hostname ->- domain ->- ip-src ->- ip-dst ->- x509-fingerprint-sha1 ->- email-src ->- email-dst ->- target-email ->- whois-registrant-email ->- whois-registrant-phone ->- text ->- whois-registrant-name ->- whois-registrar ->- whois-creation-date -- **output**: ->MISP attributes mapped from the result of the query on PassiveTotal, included in the following list: ->- hostname ->- domain ->- ip-src ->- ip-dst ->- x509-fingerprint-sha1 ->- email-src ->- email-dst ->- target-email ->- whois-registrant-email ->- whois-registrant-phone ->- text ->- whois-registrant-name ->- whois-registrar ->- whois-creation-date ->- md5 ->- sha1 ->- sha256 ->- link -- **references**: ->https://www.passivetotal.org/register -- **requirements**: ->Passivetotal python library, An access to the PassiveTotal API (apikey) - ------ - -#### [rbl](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/rbl.py) - -Module to check an IPv4 address against known RBLs. -- **features**: ->This module takes an IP address attribute as input and queries multiple know Real-time Blackhost Lists to check if they have already seen this IP address. -> ->We display then all the information we get from those different sources. -- **input**: ->IP address attribute. -- **output**: ->Text with additional data from Real-time Blackhost Lists about the IP address. -- **references**: ->[RBLs list](https://github.com/MISP/misp-modules/blob/8817de476572a10a9c9d03258ec81ca70f3d926d/misp_modules/modules/expansion/rbl.py#L20) -- **requirements**: ->dnspython3: DNS python3 library - ------ - -#### [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. -- **features**: ->The module takes an IP address as input and tries to find the hostname this IP address is resolved into. -> ->The address of the DNS resolver to use is also configurable, but if no configuration is set, we use the Google public DNS address (8.8.8.8). -> ->Please note that composite MISP attributes containing IP addresses are supported as well. -- **input**: ->An IP address attribute. -- **output**: ->Hostname attribute the input is resolved into. -- **requirements**: ->DNS python library - ------ - -#### [securitytrails](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/securitytrails.py) - - - -An expansion modules for SecurityTrails. -- **features**: ->The module takes a domain, hostname or IP address attribute as input and queries the SecurityTrails API with it. -> ->Multiple parsing operations are then processed on the result of the query to extract a much information as possible. -> ->From this data extracted are then mapped MISP attributes. -- **input**: ->A domain, hostname or IP address attribute. -- **output**: ->MISP attributes resulting from the query on SecurityTrails API, included in the following list: ->- hostname ->- domain ->- ip-src ->- ip-dst ->- dns-soa-email ->- whois-registrant-email ->- whois-registrant-phone ->- whois-registrant-name ->- whois-registrar ->- whois-creation-date ->- domain -- **references**: ->https://securitytrails.com/ -- **requirements**: ->dnstrails python library, An access to the SecurityTrails API (apikey) - ------ - -#### [shodan](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/shodan.py) - - - -Module to query on Shodan. -- **features**: ->The module takes an IP address as input and queries the Shodan API to get some additional data about it. -- **input**: ->An IP address MISP attribute. -- **output**: ->Text with additional data about the input, resulting from the query on Shodan. -- **references**: ->https://www.shodan.io/ -- **requirements**: ->shodan python library, An access to the Shodan API (apikey) - ------ - -#### [sigma_queries](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/sigma_queries.py) - - - -An expansion hover module to display the result of sigma queries. -- **features**: ->This module takes a Sigma rule attribute as input and tries all the different queries available to convert it into different formats recognized by SIEMs. -- **input**: ->A Sigma attribute. -- **output**: ->Text displaying results of queries on the Sigma attribute. -- **references**: ->https://github.com/Neo23x0/sigma/wiki -- **requirements**: ->Sigma python library - ------ - -#### [sigma_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/sigma_syntax_validator.py) - - - -An expansion hover module to perform a syntax check on sigma rules. -- **features**: ->This module takes a Sigma rule attribute as input and performs a syntax check on it. -> ->It displays then that the rule is valid if it is the case, and the error related to the rule otherwise. -- **input**: ->A Sigma attribute. -- **output**: ->Text describing the validity of the Sigma rule. -- **references**: ->https://github.com/Neo23x0/sigma/wiki -- **requirements**: ->Sigma python library, Yaml python library - ------ - -#### [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. -- **features**: ->This module takes a link or url attribute as input and caches the related web page. It returns then a link of the cached page. -- **input**: ->A link or url attribute. -- **output**: ->A malware-sample attribute describing the cached page. -- **references**: ->https://github.com/adulau/url_archiver -- **requirements**: ->urlarchiver: python library to fetch and archive URL on the file-system - ------ - -#### [stix2_pattern_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/stix2_pattern_syntax_validator.py) - - - -An expansion hover module to perform a syntax check on stix2 patterns. -- **features**: ->This module takes a STIX2 pattern attribute as input and performs a syntax check on it. -> ->It displays then that the rule is valid if it is the case, and the error related to the rule otherwise. -- **input**: ->A STIX2 pattern attribute. -- **output**: ->Text describing the validity of the STIX2 pattern. -- **references**: ->[STIX2.0 patterning specifications](http://docs.oasis-open.org/cti/stix/v2.0/cs01/part5-stix-patterning/stix-v2.0-cs01-part5-stix-patterning.html) -- **requirements**: ->stix2patterns python library - ------ - -#### [threatcrowd](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/threatcrowd.py) - - - -Module to get information from ThreatCrowd. -- **features**: ->This module takes a MISP attribute as input and queries ThreatCrowd with it. -> ->The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. -- **input**: ->A MISP attribute included in the following list: ->- hostname ->- domain ->- ip-src ->- ip-dst ->- md5 ->- sha1 ->- sha256 ->- sha512 ->- whois-registrant-email -- **output**: ->MISP attributes mapped from the result of the query on ThreatCrowd, included in the following list: ->- domain ->- ip-src ->- ip-dst ->- text ->- md5 ->- sha1 ->- sha256 ->- sha512 ->- hostname ->- whois-registrant-email -- **references**: ->https://www.threatcrowd.org/ - ------ - -#### [threatminer](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/threatminer.py) - - - -Module to get information from ThreatMiner. -- **features**: ->This module takes a MISP attribute as input and queries ThreatMiner with it. -> ->The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. -- **input**: ->A MISP attribute included in the following list: ->- hostname ->- domain ->- ip-src ->- ip-dst ->- md5 ->- sha1 ->- sha256 ->- sha512 -- **output**: ->MISP attributes mapped from the result of the query on ThreatMiner, included in the following list: ->- domain ->- ip-src ->- ip-dst ->- text ->- md5 ->- sha1 ->- sha256 ->- sha512 ->- ssdeep ->- authentihash ->- filename ->- whois-registrant-email ->- url ->- link -- **references**: ->https://www.threatminer.org/ - ------ - -#### [urlscan](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/urlscan.py) - - - -An expansion module to query urlscan.io. -- **features**: ->This module takes a MISP attribute as input and queries urlscan.io with it. -> ->The result of this query is then parsed and some data is mapped into MISP attributes in order to enrich the input attribute. -- **input**: ->A domain, hostname or url attribute. -- **output**: ->MISP attributes mapped from the result of the query on urlscan.io. -- **references**: ->https://urlscan.io/ -- **requirements**: ->An access to the urlscan.io API - ------ - -#### [virustotal](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/virustotal.py) - - - -Module to get information from virustotal. -- **features**: ->This module takes a MISP attribute as input and queries the VirusTotal API with it, in order to get additional data on the input attribute. -> ->Multiple recursive requests on the API can then be processed on some attributes found in the first request. A limit can be set to restrict the number of values to query again, and at the same time the number of request submitted to the API. -> ->This limit is important because the default user VirusTotal apikey only allows to process a certain nunmber of queries per minute. As a consequence it is recommended to have a larger number of requests or a private apikey. -> ->Data is then mapped into MISP attributes. -- **input**: ->A domain, hash (md5, sha1, sha256 or sha512), hostname or IP address attribute. -- **output**: ->MISP attributes mapped from the rersult of the query on VirusTotal API. -- **references**: ->https://www.virustotal.com/ -- **requirements**: ->An access to the VirusTotal API (apikey) - ------ - -#### [vmray_submit](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vmray_submit.py) - - - -Module to submit a sample to VMRay. -- **features**: ->This module takes an attachment or malware-sample attribute as input to query the VMRay API. -> ->The sample contained within the attribute in then enriched with data from VMRay mapped into MISP attributes. -- **input**: ->An attachment or malware-sample attribute. -- **output**: ->MISP attributes mapped from the result of the query on VMRay API, included in the following list: ->- text ->- sha1 ->- sha256 ->- md5 ->- link -- **references**: ->https://www.vmray.com/ -- **requirements**: ->An access to the VMRay API (apikey & url) - ------ - -#### [vulndb](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vulndb.py) - - - -Module to query VulnDB (RiskBasedSecurity.com). -- **features**: ->This module takes a vulnerability attribute as input and queries VulnDB in order to get some additional data about it. -> ->The API gives the result of the query which can be displayed in the screen, and/or mapped into MISP attributes to add in the event. -- **input**: ->A vulnerability attribute. -- **output**: ->Additional data enriching the CVE input, fetched from VulnDB. -- **references**: ->https://vulndb.cyberriskanalytics.com/ -- **requirements**: ->An access to the VulnDB API (apikey, apisecret) - ------ - -#### [vulners](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/vulners.py) - - - -An expansion hover module to expand information about CVE id using Vulners API. -- **features**: ->This module takes a vulnerability attribute as input and queries the Vulners API in order to get some additional data about it. -> ->The API then returns details about the vulnerability. -- **input**: ->A vulnerability attribute. -- **output**: ->Text giving additional information about the CVE in input. -- **references**: ->https://vulners.com/ -- **requirements**: ->Vulners python library, An access to the Vulners API - ------ - -#### [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). -- **features**: ->This module takes a domain or IP address attribute as input and queries a 'Univseral Whois proxy server' to get the correct details of the Whois query on the input value (check the references for more details about this whois server). -- **input**: ->A domain or IP address attribute. -- **output**: ->Text describing the result of a whois request for the input value. -- **references**: ->https://github.com/rafiot/uwhoisd -- **requirements**: ->uwhois: A whois python library - ------ - -#### [wiki](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/wiki.py) - - - -An expansion hover module to extract information from Wikidata to have additional information about particular term for analysis. -- **features**: ->This module takes a text attribute as input and queries the Wikidata API. If the text attribute is clear enough to define a specific term, the API returns a wikidata link in response. -- **input**: ->Text attribute. -- **output**: ->Text attribute. -- **references**: ->https://www.wikidata.org -- **requirements**: ->SPARQLWrapper python library - ------ - -#### [xforceexchange](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/xforceexchange.py) - - - -An expansion module for IBM X-Force Exchange. -- **features**: ->This module takes a MISP attribute as input to query the X-Force API. The API returns then additional information known in their threats data, that is mapped into MISP attributes. -- **input**: ->A MISP attribute included in the following list: ->- ip-src ->- ip-dst ->- vulnerability ->- md5 ->- sha1 ->- sha256 -- **output**: ->MISP attributes mapped from the result of the query on X-Force Exchange. -- **references**: ->https://exchange.xforce.ibmcloud.com/ -- **requirements**: ->An access to the X-Force API (apikey) - ------ - -#### [yara_query](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/yara_query.py) - - - -An expansion & hover module to translate any hash attribute into a yara rule. -- **features**: ->The module takes a hash attribute (md5, sha1, sha256, imphash) as input, and is returning a YARA rule from it. This YARA rule is also validated using the same method as in 'yara_syntax_validator' module. ->Both hover and expansion functionalities are supported with this module, where the hover part is displaying the resulting YARA rule and the expansion part allows you to add the rule as a new attribute, as usual with expansion modules. -- **input**: ->MISP Hash attribute (md5, sha1, sha256, imphash, or any of the composite attribute with filename and one of the previous hash type). -- **output**: ->YARA rule. -- **references**: ->https://virustotal.github.io/yara/, https://github.com/virustotal/yara-python -- **requirements**: ->yara-python python library - ------ - -#### [yara_syntax_validator](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/yara_syntax_validator.py) - - - -An expansion hover module to perform a syntax check on if yara rules are valid or not. -- **features**: ->This modules simply takes a YARA rule as input, and checks its syntax. It returns then a confirmation if the syntax is valid, otherwise the syntax error is displayed. -- **input**: ->YARA rule attribute. -- **output**: ->Text to inform users if their rule is valid. -- **references**: ->http://virustotal.github.io/yara/ -- **requirements**: ->yara_python python library - ------ - -## Export Modules - -#### [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**: ->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. -- **input**: ->MISP Event attributes -- **output**: ->Common Event Format file -- **references**: ->https://community.softwaregrp.com/t5/ArcSight-Connectors/ArcSight-Common-Event-Format-CEF-Guide/ta-p/1589306?attachment-id=65537 - ------ - -#### [goamlexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/goamlexport.py) - - - -This module is used to export MISP events containing transaction objects into GoAML format. -- **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. -- **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). -- **references**: ->http://goaml.unodc.org/ -- **requirements**: ->PyMISP, MISP objects - ------ - -#### [liteexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/liteexport.py) - -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 - ------ - -#### [nexthinkexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/nexthinkexport.py) - - - -Nexthink NXQL query export module -- **features**: ->This module export an event as Nexthink NXQL queries that can then be used in your own python3 tool or from wget/powershell -- **input**: ->MISP Event attributes -- **output**: ->Nexthink NXQL queries -- **references**: ->https://doc.nexthink.com/Documentation/Nexthink/latest/APIAndIntegrations/IntroducingtheWebAPIV2 - ------ - -#### [osqueryexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/osqueryexport.py) - - - -OSQuery export of a MISP event. -- **features**: ->This module export an event as osquery queries that can be used in packs or in fleet management solution like Kolide. -- **input**: ->MISP Event attributes -- **output**: ->osquery SQL queries - ------ - -#### [pdfexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/pdfexport.py) - -Simple export of a MISP event to PDF. -- **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. -- **input**: ->MISP Event -- **output**: ->MISP Event in a PDF file. -- **references**: ->https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html -- **requirements**: ->PyMISP, asciidoctor - ------ - -#### [testexport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/testexport.py) - -Skeleton export module. - ------ - -#### [threatStream_misp_export](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/threatStream_misp_export.py) - - - -Module to export a structured CSV file for uploading to threatStream. -- **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. -- **input**: ->MISP Event attributes -- **output**: ->ThreatStream CSV format file -- **references**: ->https://www.anomali.com/platform/threatstream, https://github.com/threatstream -- **requirements**: ->csv - ------ - -#### [threat_connect_export](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/export_mod/threat_connect_export.py) - - - -Module to export a structured CSV file for uploading to ThreatConnect. -- **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. -- **input**: ->MISP Event attributes -- **output**: ->ThreatConnect CSV format file -- **references**: ->https://www.threatconnect.com -- **requirements**: ->csv - ------ - -## Import Modules - -#### [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. -- **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. -- **input**: ->CSV format file. -- **output**: ->MISP Event attributes -- **references**: ->https://tools.ietf.org/html/rfc4180, https://tools.ietf.org/html/rfc7111 -- **requirements**: ->PyMISP - ------ - -#### [cuckooimport](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/cuckooimport.py) - - - -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. -- **input**: ->Cuckoo JSON file -- **output**: ->MISP Event attributes -- **references**: ->https://cuckoosandbox.org/, https://github.com/cuckoosandbox/cuckoo - ------ - -#### [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**: ->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](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/goamlimport.py) - - - -Module to import MISP objects about financial transactions from GoAML files. -- **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. -- **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. -- **references**: ->http://goaml.unodc.org/ -- **requirements**: ->PyMISP - ------ - -#### [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**: ->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 - ------ - -#### [ocr](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/ocr.py) - -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](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/openiocimport.py) - -Module to import OpenIOC packages. -- **features**: ->The module imports MISP Attributes from OpenIOC packages, there is then no special feature for users to make it work. -- **input**: ->OpenIOC packages -- **output**: ->MISP Event attributes -- **references**: ->https://www.fireeye.com/blog/threat-research/2013/10/openioc-basics.html -- **requirements**: ->PyMISP - ------ - -#### [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**: ->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. -- **input**: ->ThreatAnalyzer format file -- **output**: ->MISP Event attributes -- **references**: ->https://www.threattrack.com/malware-analysis.aspx - ------ - -#### [vmray_import](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/import_mod/vmray_import.py) - - - -Module to import VMRay (VTI) results. -- **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. -- **input**: ->VMRay format -- **output**: ->MISP Event attributes -- **references**: ->https://www.vmray.com/ -- **requirements**: ->vmray_rest_api - ------ diff --git a/doc/expansion/btc_scam_check.json b/doc/expansion/btc_scam_check.json new file mode 100644 index 0000000..44fce03 --- /dev/null +++ b/doc/expansion/btc_scam_check.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion hover module to query a special dns blacklist to check if a bitcoin address has been abused.", + "requirements": ["dnspython3: dns python library"], + "features": "The module queries a dns blacklist directly with the bitcoin address and get a response if the address has been abused.", + "logo": "logos/bitcoin.png", + "input": "btc address attribute.", + "output" : "Text to indicate if the BTC address has been abused.", + "references": ["https://btcblack.it/"] +} diff --git a/doc/expansion/btc.json b/doc/expansion/btc_steroids.json similarity index 100% rename from doc/expansion/btc.json rename to doc/expansion/btc_steroids.json diff --git a/doc/generate_documentation.py b/doc/generate_documentation.py index 980ddf6..caef84e 100644 --- a/doc/generate_documentation.py +++ b/doc/generate_documentation.py @@ -30,7 +30,7 @@ def generate_doc(root_path): 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: + with open('README.md', 'w') as w: w.write(''.join(markdown)) diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 559e5aa..2507226 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -8,4 +8,4 @@ __all__ = ['vmray_submit', 'bgpranking', 'circl_passivedns', 'circl_passivessl', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', - 'intel471'] + 'intel471', 'btc_scam_check'] diff --git a/misp_modules/modules/expansion/btc_scam_check.py b/misp_modules/modules/expansion/btc_scam_check.py new file mode 100644 index 0000000..b49414d --- /dev/null +++ b/misp_modules/modules/expansion/btc_scam_check.py @@ -0,0 +1,43 @@ +import json +import sys + +try: + from dns.resolver import Resolver, NXDOMAIN + from dns.name import LabelTooLong + resolver = Resolver() + resolver.timeout = 1 + resolver.lifetime = 1 +except ImportError: + sys.exit("dnspython3 in missing. use 'pip install dnspython3' to install it.") + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['btc'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Christian Studer', + 'description': 'Checks if a BTC address is referenced as a scam.', + 'module-type': ['hover']} +moduleconfig = [] + +url = 'bl.btcblack.it' + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + btc = request['btc'] + query = f"{btc}.{url}" + try: + result = ' - '.join([str(r) for r in resolver.query(query, 'TXT')])[1:-1] + except NXDOMAIN: + result = f"{btc} is not known as a scam address." + except LabelTooLong: + result = f"{btc} is probably not a valid BTC address." + return {'results': [{'types': mispattributes['output'], 'values': result}]} + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 08fe0cbe09e73e866f75fb348ef20f6c5e363d7f Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Tue, 5 Feb 2019 14:54:22 +0100 Subject: [PATCH 304/401] fix: Description fixed --- misp_modules/modules/expansion/btc_scam_check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/btc_scam_check.py b/misp_modules/modules/expansion/btc_scam_check.py index b49414d..9f9a7d6 100644 --- a/misp_modules/modules/expansion/btc_scam_check.py +++ b/misp_modules/modules/expansion/btc_scam_check.py @@ -13,7 +13,7 @@ except ImportError: misperrors = {'error': 'Error'} mispattributes = {'input': ['btc'], 'output': ['text']} moduleinfo = {'version': '0.1', 'author': 'Christian Studer', - 'description': 'Checks if a BTC address is referenced as a scam.', + 'description': 'Checks if a BTC address has been abused.', 'module-type': ['hover']} moduleconfig = [] From e4c14689683c5b069e7707f578e80499de8b0997 Mon Sep 17 00:00:00 2001 From: 9b Date: Fri, 8 Feb 2019 12:27:20 -0500 Subject: [PATCH 305/401] Stubbed module --- README.md | 1 + REQUIREMENTS | 1 + misp_modules/modules/expansion/__init__.py | 2 +- .../modules/expansion/backscatter_io.py | 77 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 misp_modules/modules/expansion/backscatter_io.py diff --git a/README.md b/README.md index 368ef6f..59f2346 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ### Expansion modules +* [Backscatter.io](misp_modules/modules/expansion/backscatter_io) - a hover and expansion module to expand an IP address with mass-scanning observations. * [BGP Ranking](misp_modules/modules/expansion/bgpranking.py) - a hover and expansion module to expand an AS number with the ASN description, its history, and position in BGP Ranking. * [BTC transactions](misp_modules/modules/expansion/btc_steroids.py) - An expansion hover module to get a blockchain balance and the transactions from a BTC address in MISP. * [CIRCL Passive DNS](misp_modules/modules/expansion/circl_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. diff --git a/REQUIREMENTS b/REQUIREMENTS index c3c16e6..0720e90 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -11,6 +11,7 @@ aiohttp==3.4.4 antlr4-python3-runtime==4.7.2 ; python_version >= '3' async-timeout==3.0.1 attrs==18.2.0 +backscatter==0.2.3 beautifulsoup4==4.7.1 blockchain==1.4.4 certifi==2018.11.29 diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 559e5aa..b6bc74d 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -8,4 +8,4 @@ __all__ = ['vmray_submit', 'bgpranking', 'circl_passivedns', 'circl_passivessl', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', - 'intel471'] + 'intel471', 'backscatter_io'] diff --git a/misp_modules/modules/expansion/backscatter_io.py b/misp_modules/modules/expansion/backscatter_io.py new file mode 100644 index 0000000..2af073e --- /dev/null +++ b/misp_modules/modules/expansion/backscatter_io.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +"""Backscatter.io Module.""" +import json +try: + from backscatter import Backscatter +except ImportError: + print("Backscatter.io library not installed.") + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['ip-src', 'ip-dst'], 'output': ['freetext']} +moduleinfo = {'version': '1', 'author': 'brandon@backscatter.io', + 'description': 'Backscatter.io module to bring mass-scanning observations into MISP.', + 'module-type': ['expansion', 'hover']} +moduleconfig = ['api_key'] +query_playbook = [ + {'inputs': ['ip-src', 'ip-dst'], + 'services': ['observations', 'enrichment'], + 'name': 'generic'} +] + + +def check_query(request): + """Check the incoming request for a valid configuration.""" + output = {'success': False} + config = request.get('config', None) + if not config: + misperrors['error'] = "Configuration is missing from the request." + return output + for item in moduleconfig: + if config.get(item, None): + continue + misperrors['error'] = "Backscatter.io authentication is missing." + return output + if not request.get('ip-src') and request.get('ip-dst'): + misperrors['error'] = "Unsupported attributes type." + return output + profile = {'success': True, 'config': config, 'playbook': 'generic'} + if 'ip-src' in request: + profile.update({'value': request.get('ip-src')}) + else: + profile.update({'value': request.get('ip-dst')}) + return profile + + +def handler(q=False): + """Handle gathering data.""" + if not q: + return q + request = json.loads(q) + checks = check_query(request) + if not checks['success']: + return misperrors + + output = {'results': list()} + + try: + bs = Backscatter(checks['config']['api_key']) + response = bs.get_observations(query=output['value'], query_type='ip') + if not response['success']: + misperrors['error'] = '%s: %s' % (response['error'], response['message']) + return misperrors + r = {'results': [{'types': mispattributes['output'], 'values': [str(response)]}]} + except Exception, e: + misperrors['error'] = str(e) + return misperrors + + return output + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo + From c8b410161a19f0edbaedefd80d860ddc0cab326d Mon Sep 17 00:00:00 2001 From: 9b Date: Fri, 8 Feb 2019 12:29:43 -0500 Subject: [PATCH 306/401] Use the write var on return --- misp_modules/modules/expansion/backscatter_io.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/backscatter_io.py b/misp_modules/modules/expansion/backscatter_io.py index 2af073e..dab07a7 100644 --- a/misp_modules/modules/expansion/backscatter_io.py +++ b/misp_modules/modules/expansion/backscatter_io.py @@ -51,15 +51,13 @@ def handler(q=False): if not checks['success']: return misperrors - output = {'results': list()} - try: bs = Backscatter(checks['config']['api_key']) response = bs.get_observations(query=output['value'], query_type='ip') if not response['success']: misperrors['error'] = '%s: %s' % (response['error'], response['message']) return misperrors - r = {'results': [{'types': mispattributes['output'], 'values': [str(response)]}]} + output = {'results': [{'types': mispattributes['output'], 'values': [str(response)]}]} except Exception, e: misperrors['error'] = str(e) return misperrors From acc35e3a02362b2cddfdec31d48a0c740e4ecbac Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Sun, 10 Feb 2019 16:33:09 +0100 Subject: [PATCH 307/401] chg: [backscatter.io] Exception handler fixed for recent version of Python --- misp_modules/modules/expansion/backscatter_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/backscatter_io.py b/misp_modules/modules/expansion/backscatter_io.py index dab07a7..5a8a9cd 100644 --- a/misp_modules/modules/expansion/backscatter_io.py +++ b/misp_modules/modules/expansion/backscatter_io.py @@ -58,7 +58,7 @@ def handler(q=False): misperrors['error'] = '%s: %s' % (response['error'], response['message']) return misperrors output = {'results': [{'types': mispattributes['output'], 'values': [str(response)]}]} - except Exception, e: + except Exception as e: misperrors['error'] = str(e) return misperrors From 7b1a837b109f0ff1a8c9240bb31a84753739f438 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Sun, 10 Feb 2019 16:40:06 +0100 Subject: [PATCH 308/401] chg: [backscatter.io] remove blank line at the end of the file --- misp_modules/modules/expansion/backscatter_io.py | 1 - 1 file changed, 1 deletion(-) diff --git a/misp_modules/modules/expansion/backscatter_io.py b/misp_modules/modules/expansion/backscatter_io.py index 5a8a9cd..bfa04f6 100644 --- a/misp_modules/modules/expansion/backscatter_io.py +++ b/misp_modules/modules/expansion/backscatter_io.py @@ -72,4 +72,3 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig return moduleinfo - From 30753d57aa3096ce85c3ed54eed6e0f2dc3d054c Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Sun, 10 Feb 2019 16:46:43 +0100 Subject: [PATCH 309/401] chg: [doc] backscatter.io documentation added --- doc/expansion/backscatter_io.json | 8 ++++++++ doc/logos/backscatter_io.png | Bin 0 -> 25590 bytes 2 files changed, 8 insertions(+) create mode 100644 doc/expansion/backscatter_io.json create mode 100644 doc/logos/backscatter_io.png diff --git a/doc/expansion/backscatter_io.json b/doc/expansion/backscatter_io.json new file mode 100644 index 0000000..22123a5 --- /dev/null +++ b/doc/expansion/backscatter_io.json @@ -0,0 +1,8 @@ +{ + "description": "Query backscatter.io (https://backscatter.io/).", + "requirements": ["backscatter python library"], + "features": "The module takes a source or destination IP address as input and displays the information known by backscatter.io.\n\n", + "references": ["https://pypi.org/project/backscatter/"], + "input": "IP addresses.", + "output": "Text containing a history of the IP addresses especially on scanning based on backscatter.io information ." +} diff --git a/doc/logos/backscatter_io.png b/doc/logos/backscatter_io.png new file mode 100644 index 0000000000000000000000000000000000000000..0973112835e7f4ce67375c3c2b51e2fc23628175 GIT binary patch literal 25590 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 Date: Sun, 10 Feb 2019 16:47:42 +0100 Subject: [PATCH 310/401] chg: [doc] backscatter.io updated --- doc/documentation.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/documentation.md b/doc/documentation.md index 31f09ed..23b4bce 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -2,6 +2,24 @@ ## Expansion Modules +#### [backscatter_io](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/backscatter_io.py) + +Query backscatter.io (https://backscatter.io/). +- **features**: +>The module takes a source or destination IP address as input and displays the information known by backscatter.io. +> +> +- **input**: +>IP addresses. +- **output**: +>Text containing a history of the IP addresses especially on scanning based on backscatter.io information . +- **references**: +>https://pypi.org/project/backscatter/ +- **requirements**: +>backscatter python library + +----- + #### [bgpranking](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/bgpranking.py) Query BGP Ranking (https://bgpranking-ng.circl.lu/). From f0ccfd2027f19378c681ab4d5f79191bae3c43cb Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Sun, 10 Feb 2019 16:56:01 +0100 Subject: [PATCH 311/401] chg: [backscatter.io] blind fix regarding undefined value --- misp_modules/modules/expansion/backscatter_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/backscatter_io.py b/misp_modules/modules/expansion/backscatter_io.py index bfa04f6..0796917 100644 --- a/misp_modules/modules/expansion/backscatter_io.py +++ b/misp_modules/modules/expansion/backscatter_io.py @@ -53,7 +53,7 @@ def handler(q=False): try: bs = Backscatter(checks['config']['api_key']) - response = bs.get_observations(query=output['value'], query_type='ip') + response = bs.get_observations(query=checks['value'], query_type='ip') if not response['success']: misperrors['error'] = '%s: %s' % (response['error'], response['message']) return misperrors From 61c0274f78907170d50dfef5c2659b6e4b35c3ae Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 11 Feb 2019 09:32:53 +0100 Subject: [PATCH 312/401] fix: Regenerated documentation --- doc/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/README.md b/doc/README.md index e47470d..44bb5b0 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,6 +2,24 @@ ## Expansion Modules +#### [backscatter_io](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/backscatter_io.py) + +Query backscatter.io (https://backscatter.io/). +- **features**: +>The module takes a source or destination IP address as input and displays the information known by backscatter.io. +> +> +- **input**: +>IP addresses. +- **output**: +>Text containing a history of the IP addresses especially on scanning based on backscatter.io information . +- **references**: +>https://pypi.org/project/backscatter/ +- **requirements**: +>backscatter python library + +----- + #### [bgpranking](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/bgpranking.py) Query BGP Ranking (https://bgpranking-ng.circl.lu/). From e940070956432cd7aaba0c4a8fad9c49839792e2 Mon Sep 17 00:00:00 2001 From: chrisr3d Date: Mon, 11 Feb 2019 09:43:25 +0100 Subject: [PATCH 313/401] add: [doc] Added backscatter.io logo + regenerated documentation --- doc/README.md | 2 ++ doc/expansion/backscatter_io.json | 1 + 2 files changed, 3 insertions(+) diff --git a/doc/README.md b/doc/README.md index 44bb5b0..c32a8c4 100644 --- a/doc/README.md +++ b/doc/README.md @@ -4,6 +4,8 @@ #### [backscatter_io](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/backscatter_io.py) + + Query backscatter.io (https://backscatter.io/). - **features**: >The module takes a source or destination IP address as input and displays the information known by backscatter.io. diff --git a/doc/expansion/backscatter_io.json b/doc/expansion/backscatter_io.json index 22123a5..a8475c5 100644 --- a/doc/expansion/backscatter_io.json +++ b/doc/expansion/backscatter_io.json @@ -2,6 +2,7 @@ "description": "Query backscatter.io (https://backscatter.io/).", "requirements": ["backscatter python library"], "features": "The module takes a source or destination IP address as input and displays the information known by backscatter.io.\n\n", + "logo": "logos/backscatter_io.png", "references": ["https://pypi.org/project/backscatter/"], "input": "IP addresses.", "output": "Text containing a history of the IP addresses especially on scanning based on backscatter.io information ." From 0bf27c1b69b12ea4bbefad830a89791d558eca07 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 11 Feb 2019 14:23:18 +0100 Subject: [PATCH 314/401] chg: [btc_scam_check] fix spacing for making flake 8 happy --- misp_modules/modules/expansion/btc_scam_check.py | 1 + 1 file changed, 1 insertion(+) diff --git a/misp_modules/modules/expansion/btc_scam_check.py b/misp_modules/modules/expansion/btc_scam_check.py index 9f9a7d6..f551926 100644 --- a/misp_modules/modules/expansion/btc_scam_check.py +++ b/misp_modules/modules/expansion/btc_scam_check.py @@ -19,6 +19,7 @@ moduleconfig = [] url = 'bl.btcblack.it' + def handler(q=False): if q is False: return False From 9abc3a4b0a323cdd2617be673847422ad8d39fb5 Mon Sep 17 00:00:00 2001 From: iwitz Date: Fri, 15 Feb 2019 10:16:52 +0100 Subject: [PATCH 315/401] add: rhel installation instructions --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b60accd..ee4f2f8 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ sudo sed -i -e '$i \sudo -u www-data /var/www/MISP/venv/bin/misp-modules -l 127. /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules ~~~~ -## How to install and start MISP modules? +## How to install and start MISP modules on Debian-based distributions ? ~~~~bash sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick @@ -115,6 +115,42 @@ sudo sed -i -e '$i \sudo -u www-data /var/www/MISP/venv/bin/misp-modules -l 127. /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules ~~~~ +## How to install and start MISP modules on RHEL-based distributions ? +As of this writing, the official RHEL repositories only contain Ruby 2.0.0 and Ruby 2.1 or higher is required. As such, this guide installs Ruby 2.2 from the [SCL](https://access.redhat.com/documentation/en-us/red_hat_software_collections/3/html/3.2_release_notes/chap-installation#sect-Installation-Subscribe) repository. +~~~~bash +yum install rh-ruby22 +cd /var/www/MISP +git clone https://github.com/MISP/misp-modules.git +cd misp-modules +scl enable rh-python36 ‘python3 –m pip install cryptography’ +scl enable rh-python36 ‘python3 –m pip install -I -r REQUIREMENTS’ +scl enable rh-python36 ‘python3 –m pip install –I .’ +scl enable rh-ruby22 ‘gem install asciidoctor-pdf –pre’ +~~~~ +Create the service file /etc/systemd/system/misp-workers.service : +~~~~ +[Unit] +Description=MISP's modules +After=misp-workers.service + +[Service] +Type=simple +User=apache +Group=apache +ExecStart=/usr/bin/scl enable rh-python36 rh-ruby22 ‘/opt/rh/rh-python36/root/bin/misp-modules –l 127.0.0.1 –s’ +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +~~~~ +The `After=misp-workers.service` must be changed or removed if you have not created a misp-workers service. +Then, enable the misp-modules service and start it ; +~~~~bash +systemctl daemon-reload +systemctl enable --now misp-modules +~~~~ + ## How to add your own MISP modules? Create your module in [misp_modules/modules/expansion/](misp_modules/modules/expansion/), [misp_modules/modules/export_mod/](misp_modules/modules/export_mod/), or [misp_modules/modules/import_mod/](misp_modules/modules/import_mod/). The module should have at minimum three functions: From 2753f354ab3291985b8beb9aa745fe30d1e6b2a8 Mon Sep 17 00:00:00 2001 From: Vincent-CIRCL Date: Mon, 18 Feb 2019 14:27:16 +0100 Subject: [PATCH 316/401] test update --- misp_modules/modules/export_mod/pdfexport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index df7f879..1b4c731 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -12,7 +12,7 @@ from pymisp import MISPEvent misperrors = {'error': 'Error'} -moduleinfo = {'version': '1', +moduleinfo = {'version': '42', 'author': 'Raphaël Vinot', 'description': 'Simple export to PDF', 'module-type': ['export'], From be01d547791f7f9950dad170b3908155e15ff3c8 Mon Sep 17 00:00:00 2001 From: Vincent-CIRCL Date: Mon, 18 Feb 2019 15:23:57 +0100 Subject: [PATCH 317/401] print values --- misp_modules/modules/export_mod/pdfexport.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 1b4c731..074e473 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -144,6 +144,8 @@ def handler(q=False): return False for evt in request['data']: + print(request['data']) + report = ReportGenerator() report.report_headers() report.from_event(evt) From 5d824a24f9f1348f78da8e6a5e7c61329db26bb8 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Thu, 21 Feb 2019 10:06:28 +0530 Subject: [PATCH 318/401] chg: [pypi] Made sure url-normalize installs less stric. --- REQUIREMENTS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index 0720e90..7c8dde3 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -54,7 +54,8 @@ soupsieve==1.7.3 sparqlwrapper==1.8.2 stix2-patterns==1.1.0 tornado==5.1.1 -url-normalize==1.4.1 +# Python 3.5 only provides url-normalize v1.3.3 +url-normalize>=1.3.3 urlarchiver==0.2 urllib3==1.24.1 vulners==1.4.0 From 2d29ce11bbf8dadd82fe886330476488df32c140 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Thu, 21 Feb 2019 15:42:18 +0100 Subject: [PATCH 319/401] Test 1 - PDF call --- misp_modules/modules/export_mod/pdfexport.py | 68 +++++++------------- 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 074e473..23d0edd 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -7,61 +7,26 @@ import shlex import subprocess import base64 -from pymisp import MISPEvent +from pymisp import MISPEvent, reportlab_generator misperrors = {'error': 'Error'} -moduleinfo = {'version': '42', - 'author': 'Raphaël Vinot', +moduleinfo = {'version': '2', + 'author': 'Vincent Falconieri (prev. Raphaël Vinot)', 'description': 'Simple export to PDF', 'module-type': ['export'], 'require_standard_format': True} moduleconfig = [] - mispattributes = {} + outputFileExtension = "pdf" responseType = "application/pdf" types_to_attach = ['ip-dst', 'url', 'domain'] objects_to_attach = ['domain-ip'] -headers = """ -:toc: right -:toclevels: 1 -:toc-title: Daily Report -:icons: font -:sectanchors: -:sectlinks: -= Daily report by {org_name} -{date} - -:icons: font - -""" - -event_level_tags = """ -IMPORTANT: This event is classified TLP:{value}. - -{expanded} - -""" - -attributes = """ -=== Indicator(s) of compromise - -{list_attributes} - -""" - -title = """ -== ({internal_id}) {title} - -{summary} - -""" - class ReportGenerator(): def __init__(self): @@ -79,6 +44,9 @@ class ReportGenerator(): self.misp_event = MISPEvent() self.misp_event.load(event) + ''' + + def attributes(self): if not self.misp_event.attributes: return '' @@ -132,7 +100,7 @@ class ReportGenerator(): self.report += self.title() self.report += self.event_level_tags() self.report += self.attributes() - + ''' def handler(q=False): if q is False: @@ -144,19 +112,27 @@ def handler(q=False): return False for evt in request['data']: + + ''' + print(" DATA ") print(request['data']) + + reportlab_generator. report = ReportGenerator() report.report_headers() report.from_event(evt) report.asciidoc() - command_line = 'asciidoctor-pdf -' - args = shlex.split(command_line) - with subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE) as process: - cmd_out, cmd_err = process.communicate( - input=report.report.encode('utf-8')) - return {'response': [], 'data': str(base64.b64encode(cmd_out), 'utf-8')} + print(" REPORT : ") + print(report) + ''' + misp_event = MISPEvent() + misp_event.load(request['data']) + + pdf = reportlab_generator.get_base64_from_buffer(reportlab_generator.convert_event_in_pdf_buffer(misp_event)) + + return {'response': [], 'data': str(pdf, 'utf-8')} def introspection(): From a93b34208f358c76184b725f656f43f39dbd8e18 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 22 Feb 2019 10:14:22 +0100 Subject: [PATCH 320/401] fix: [pdfexport] Bugfix on PyMisp exportpdf call --- misp_modules/modules/export_mod/pdfexport.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 23d0edd..cb4e297 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -7,8 +7,10 @@ import shlex import subprocess import base64 -from pymisp import MISPEvent, reportlab_generator +print("test PDF pdf export (reportlab generator import)") +from pymisp import MISPEvent +from pymisp.tools import reportlab_generator misperrors = {'error': 'Error'} @@ -45,8 +47,6 @@ class ReportGenerator(): self.misp_event.load(event) ''' - - def attributes(self): if not self.misp_event.attributes: return '' @@ -127,12 +127,13 @@ def handler(q=False): print(" REPORT : ") print(report) ''' - misp_event = MISPEvent() - misp_event.load(request['data']) - pdf = reportlab_generator.get_base64_from_buffer(reportlab_generator.convert_event_in_pdf_buffer(misp_event)) + misp_event = MISPEvent() + misp_event.load(evt) - return {'response': [], 'data': str(pdf, 'utf-8')} + pdf = reportlab_generator.get_base64_from_value(reportlab_generator.convert_event_in_pdf_buffer(misp_event)) + + return {'response': [], 'data': str(pdf, 'utf-8')} def introspection(): @@ -164,3 +165,8 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig return moduleinfo + +import pprint + +if __name__ == "__main__": + pprint.pprint("test") \ No newline at end of file From 40cd32f1b8c073caf20d133ca0e9780ec2f602cc Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 22 Feb 2019 10:25:12 +0100 Subject: [PATCH 321/401] tidy: Remove old dead export code --- misp_modules/modules/export_mod/pdfexport.py | 78 -------------------- 1 file changed, 78 deletions(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index cb4e297..ef3d775 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -7,8 +7,6 @@ import shlex import subprocess import base64 -print("test PDF pdf export (reportlab generator import)") - from pymisp import MISPEvent from pymisp.tools import reportlab_generator @@ -46,62 +44,6 @@ class ReportGenerator(): self.misp_event = MISPEvent() self.misp_event.load(event) - ''' - def attributes(self): - if not self.misp_event.attributes: - return '' - list_attributes = [] - for attribute in self.misp_event.attributes: - if attribute.type in types_to_attach: - list_attributes.append("* {}".format(attribute.value)) - for obj in self.misp_event.Object: - if obj.name in objects_to_attach: - for attribute in obj.Attribute: - if attribute.type in types_to_attach: - list_attributes.append("* {}".format(attribute.value)) - return attributes.format(list_attributes="\n".join(list_attributes)) - - def _get_tag_info(self, machinetag): - return self.taxonomies.revert_machinetag(machinetag) - - def report_headers(self): - content = {'org_name': 'name', - 'date': date.today().isoformat()} - self.report += headers.format(**content) - - def event_level_tags(self): - if not self.misp_event.Tag: - return '' - for tag in self.misp_event.Tag: - # Only look for TLP for now - if tag['name'].startswith('tlp'): - tax, predicate = self._get_tag_info(tag['name']) - return self.event_level_tags.format(value=predicate.predicate.upper(), expanded=predicate.expanded) - - def title(self): - internal_id = '' - summary = '' - # Get internal refs for report - if not hasattr(self.misp_event, 'Object'): - return '' - for obj in self.misp_event.Object: - if obj.name != 'report': - continue - for a in obj.Attribute: - if a.object_relation == 'case-number': - internal_id = a.value - if a.object_relation == 'summary': - summary = a.value - - return title.format(internal_id=internal_id, title=self.misp_event.info, - summary=summary) - - def asciidoc(self, lang='en'): - self.report += self.title() - self.report += self.event_level_tags() - self.report += self.attributes() - ''' - def handler(q=False): if q is False: return False @@ -113,21 +55,6 @@ def handler(q=False): for evt in request['data']: - ''' - print(" DATA ") - print(request['data']) - - reportlab_generator. - - report = ReportGenerator() - report.report_headers() - report.from_event(evt) - report.asciidoc() - - print(" REPORT : ") - print(report) - ''' - misp_event = MISPEvent() misp_event.load(evt) @@ -165,8 +92,3 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig return moduleinfo - -import pprint - -if __name__ == "__main__": - pprint.pprint("test") \ No newline at end of file From 9f0f6e71e87f658ebb4518f9c9142db1cf0efe1e Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Fri, 22 Feb 2019 12:15:28 +0100 Subject: [PATCH 322/401] chg: PyMISP requirement --- REQUIREMENTS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index 0720e90..6f3d1b2 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -3,7 +3,7 @@ -e git+https://github.com/D4-project/BGP-Ranking.git/@7e698f87366e6f99b4d0d11852737db28e3ddc62#egg=pybgpranking&subdirectory=client -e git+https://github.com/D4-project/IPASN-History.git/@e846cd36fe1ed6b22f60890bba89f84e61b62e59#egg=pyipasnhistory&subdirectory=client -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 --e git+https://github.com/MISP/PyMISP.git@2c877f2aec11b7f5d2f23dfc5ce7398b2ce33b48#egg=pymisp +-e git+https://github.com/MISP/PyMISP.git@ccd7565d3ce4693b96ea2352792099b40c53e494#egg=pymisp -e git+https://github.com/Rafiot/uwhoisd.git@f6f035e52213c8abc20f2084d28cfffb399457cb#egg=uwhois&subdirectory=client -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails -e git+https://github.com/sebdraven/pyonyphe@66329baeee7cab844f2203c047c2551828eaf14d#egg=pyonyphe From 66ee78e7af41f7062e3239e7fe676c80dc8a378d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Sun, 24 Feb 2019 16:02:13 -0800 Subject: [PATCH 323/401] new: Add systemd launcher --- etc/systemd/system/misp-modules.service | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 etc/systemd/system/misp-modules.service diff --git a/etc/systemd/system/misp-modules.service b/etc/systemd/system/misp-modules.service new file mode 100644 index 0000000..3ff05ae --- /dev/null +++ b/etc/systemd/system/misp-modules.service @@ -0,0 +1,14 @@ +[Unit] +Description=System-wide instance of the MISP Modules +After=network.target + +[Service] +User=www-data +Group=www-data +WorkingDirectory=/usr/local/src/misp-modules +Environment="PATH=/var/www/MISP/venv/bin" +ExecStart=misp-modules -l 127.0.0.1 -s + +[Install] +WantedBy=multi-user.target + From 43d2ae6203a484e4614166a610a3d6bc73c12b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Sun, 24 Feb 2019 18:20:28 -0800 Subject: [PATCH 324/401] fix: systemd service --- etc/systemd/system/misp-modules.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/systemd/system/misp-modules.service b/etc/systemd/system/misp-modules.service index 3ff05ae..99cd102 100644 --- a/etc/systemd/system/misp-modules.service +++ b/etc/systemd/system/misp-modules.service @@ -7,7 +7,7 @@ User=www-data Group=www-data WorkingDirectory=/usr/local/src/misp-modules Environment="PATH=/var/www/MISP/venv/bin" -ExecStart=misp-modules -l 127.0.0.1 -s +ExecStart=/var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s [Install] WantedBy=multi-user.target From a3a871f2faa6b27c79e8c55bd0b128b9edbc7cf3 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Mon, 25 Feb 2019 15:51:33 +0100 Subject: [PATCH 325/401] fix [exportpdf] update parameters for links generation --- misp_modules/modules/export_mod/pdfexport.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index ef3d775..977ee87 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -18,7 +18,9 @@ moduleinfo = {'version': '2', 'module-type': ['export'], 'require_standard_format': True} -moduleconfig = [] +# config fields that your code expects from the site admin +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] + mispattributes = {} outputFileExtension = "pdf" @@ -53,12 +55,19 @@ def handler(q=False): if 'data' not in request: return False + config = {} + + # Construct config object for reportlab_generator + for config_item in moduleconfig : + if (request.get('config')) and (request['config'].get(config_item) is not None): + config[config_item] = request['config'].get(config_item) + for evt in request['data']: misp_event = MISPEvent() misp_event.load(evt) - pdf = reportlab_generator.get_base64_from_value(reportlab_generator.convert_event_in_pdf_buffer(misp_event)) + pdf = reportlab_generator.get_base64_from_value(reportlab_generator.convert_event_in_pdf_buffer(misp_event, config)) return {'response': [], 'data': str(pdf, 'utf-8')} From 0d8ead483e204045eaff9af35bc61836488c30fc Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 25 Feb 2019 16:18:41 +0100 Subject: [PATCH 326/401] chg: [PyMISP] dep updated to the latest version --- REQUIREMENTS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index 6f3d1b2..e42481b 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -3,7 +3,7 @@ -e git+https://github.com/D4-project/BGP-Ranking.git/@7e698f87366e6f99b4d0d11852737db28e3ddc62#egg=pybgpranking&subdirectory=client -e git+https://github.com/D4-project/IPASN-History.git/@e846cd36fe1ed6b22f60890bba89f84e61b62e59#egg=pyipasnhistory&subdirectory=client -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 --e git+https://github.com/MISP/PyMISP.git@ccd7565d3ce4693b96ea2352792099b40c53e494#egg=pymisp +-e git+https://github.com/MISP/PyMISP.git@345f055844fed0acdfb34c52d96d1751728bb82c#egg=pymisp -e git+https://github.com/Rafiot/uwhoisd.git@f6f035e52213c8abc20f2084d28cfffb399457cb#egg=uwhois&subdirectory=client -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails -e git+https://github.com/sebdraven/pyonyphe@66329baeee7cab844f2203c047c2551828eaf14d#egg=pyonyphe From 9e48b3994a70cc9447c279470a3dce9b23a1d278 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 25 Feb 2019 20:29:04 +0100 Subject: [PATCH 327/401] chg: [requirements] updated --- REQUIREMENTS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REQUIREMENTS b/REQUIREMENTS index e42481b..69b0568 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -3,7 +3,7 @@ -e git+https://github.com/D4-project/BGP-Ranking.git/@7e698f87366e6f99b4d0d11852737db28e3ddc62#egg=pybgpranking&subdirectory=client -e git+https://github.com/D4-project/IPASN-History.git/@e846cd36fe1ed6b22f60890bba89f84e61b62e59#egg=pyipasnhistory&subdirectory=client -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 --e git+https://github.com/MISP/PyMISP.git@345f055844fed0acdfb34c52d96d1751728bb82c#egg=pymisp +-e git+https://github.com/MISP/PyMISP.git@634ecc3ac308d01ebf5f5fbb9aace7746a2b8707#egg=pymisp -e git+https://github.com/Rafiot/uwhoisd.git@f6f035e52213c8abc20f2084d28cfffb399457cb#egg=uwhois&subdirectory=client -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails -e git+https://github.com/sebdraven/pyonyphe@66329baeee7cab844f2203c047c2551828eaf14d#egg=pyonyphe From bbe7fe51e70ee7ef24fb9a9573d7ad5a85ddaf9f Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 25 Feb 2019 20:34:48 +0100 Subject: [PATCH 328/401] chg: [pipenv] Pipfile.lock updated --- Pipfile.lock | 174 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 106 insertions(+), 68 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 19f32f0..1c08572 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -150,9 +150,9 @@ }, "httplib2": { "hashes": [ - "sha256:f61fb838a94ce3b349aa32c92fd8430f7e3511afdb18bf9640d647e30c90a6d6" + "sha256:4ba6b8fd77d0038769bf3c33c9a96a6f752bc4cdf739701fdcaf210121f399d4" ], - "version": "==0.12.0" + "version": "==0.12.1" }, "idna": { "hashes": [ @@ -177,10 +177,10 @@ }, "jsonschema": { "hashes": [ - "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", - "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" + "sha256:acc8a90c31d11060516cfd0b414b9f8bcf4bc691b21f0f786ea57dd5255c79db", + "sha256:dd3f8ecb1b52d94d45eedb67cb86cac57b94ded562c5d98f63719e55ce58557b" ], - "version": "==2.6.0" + "version": "==3.0.0" }, "maclookup": { "hashes": [ @@ -281,22 +281,22 @@ }, "psutil": { "hashes": [ - "sha256:04d2071100aaad59f9bcbb801be2125d53b2e03b1517d9fed90b45eea51d297e", - "sha256:1aba93430050270750d046a179c5f3d6e1f5f8b96c20399ba38c596b28fc4d37", - "sha256:3ac48568f5b85fee44cd8002a15a7733deca056a191d313dbf24c11519c0c4a8", - "sha256:96f3fdb4ef7467854d46ad5a7e28eb4c6dc6d455d751ddf9640cd6d52bdb03d7", - "sha256:b755be689d6fc8ebc401e1d5ce5bac867e35788f10229e166338484eead51b12", - "sha256:c8ee08ad1b716911c86f12dc753eb1879006224fd51509f077987bb6493be615", - "sha256:d0c4230d60376aee0757d934020b14899f6020cd70ef8d2cb4f228b6ffc43e8f", - "sha256:d23f7025bac9b3e38adc6bd032cdaac648ac0074d18e36950a04af35458342e8", - "sha256:f0fcb7d3006dd4d9ccf3ccd0595d44c6abbfd433ec31b6ca177300ee3f19e54e" + "sha256:5ce6b5eb0267233459f4d3980c205828482f450999b8f5b684d9629fea98782a", + "sha256:72cebfaa422b7978a1d3632b65ff734a34c6b34f4578b68a5c204d633756b810", + "sha256:77c231b4dff8c1c329a4cd1c22b96c8976c597017ff5b09993cd148d6a94500c", + "sha256:8846ab0be0cdccd6cc92ecd1246a16e2f2e49f53bd73e522c3a75ac291e1b51d", + "sha256:a013b4250ccbddc9d22feca0f986a1afc71717ad026c0f2109bbffd007351191", + "sha256:ad43b83119eeea6d5751023298cd331637e542cbd332196464799e25a5519f8f", + "sha256:c177777c787d247d02dae6c855330f9ed3e1abf8ca1744c26dd5ff968949999a", + "sha256:ec1ef313530a9457e48d25e3fdb1723dfa636008bf1b970027462d46f2555d59", + "sha256:ef3e5e02b3c5d1df366abe7b4820400d5c427579668ad4465ff189d28ded5ebd" ], - "version": "==5.5.0" + "version": "==5.5.1" }, "pybgpranking": { "editable": true, "git": "https://github.com/D4-project/BGP-Ranking.git/", - "ref": "7e698f87366e6f99b4d0d11852737db28e3ddc62", + "ref": "37c97ae252ec4bf1d67733a49d4895c8cb009cf9", "subdirectory": "client" }, "pydnstrails": { @@ -333,12 +333,12 @@ "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "2c877f2aec11b7f5d2f23dfc5ce7398b2ce33b48" + "ref": "634ecc3ac308d01ebf5f5fbb9aace7746a2b8707" }, "pyonyphe": { "editable": true, "git": "https://github.com/sebdraven/pyonyphe", - "ref": "66329baeee7cab844f2203c047c2551828eaf14d" + "ref": "cbb0168d5cb28a9f71f7ab3773164a7039ccdb12" }, "pyparsing": { "hashes": [ @@ -361,6 +361,12 @@ "index": "pypi", "version": "==2.1" }, + "pyrsistent": { + "hashes": [ + "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2" + ], + "version": "==0.14.11" + }, "pytesseract": { "hashes": [ "sha256:11c20321595b6e2e904b594633edf1a717212b13bac7512986a2d807b8849770" @@ -370,10 +376,10 @@ }, "python-dateutil": { "hashes": [ - "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93", - "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02" + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" ], - "version": "==2.7.5" + "version": "==2.8.0" }, "pyyaml": { "hashes": [ @@ -400,10 +406,43 @@ }, "redis": { "hashes": [ - "sha256:74c892041cba46078ae1ef845241548baa3bd3634f9a6f0f952f006eb1619c71", - "sha256:7ba8612bbfd966dea8c62322543fed0095da2834dbd5a7c124afbc617a156aa7" + "sha256:724932360d48e5407e8f82e405ab3650a36ed02c7e460d1e6fddf0f038422b54", + "sha256:9b19425a38fd074eb5795ff2b0d9a55b46a44f91f5347995f27e3ad257a7d775" ], - "version": "==3.1.0" + "version": "==3.2.0" + }, + "reportlab": { + "hashes": [ + "sha256:069f684cd0aaa518a27dc9124aed29cee8998e21ddf19604e53214ec8462bdd7", + "sha256:09b68ec01d86b4b120456b3f3202570ec96f57624e3a4fc36f3829323391daa4", + "sha256:0c32be9a406172c29ea20ff55a709ccac1e7fb09f15aba67cb7b455fd1d3dbe0", + "sha256:233196cf25e97cfe7c452524ea29d9a4909f1cb66599299233be1efaaaa7a7a3", + "sha256:2b5e4533f3e5b962835a5ce44467e66d1ecc822761d1b508077b5087a06be338", + "sha256:2e860bcdace5a558356802a92ae8658d7e5fdaa00ded82e83a3f2987c562cb66", + "sha256:3546029e63a9a9dc24ee38959eb417678c2425b96cd27b31e09e216dafc94666", + "sha256:4452b93f9c73b6b70311e7d69082d64da81b38e91bfb4766397630092e6da6fd", + "sha256:528c74a1c6527d1859c2c7a64a94a1cba485b00175162ea23699ae58a1e94939", + "sha256:6116e750f98018febc08dfee6df20446cf954adbcfa378d2c703d56c8864aff3", + "sha256:6b2b3580c647d75ef129172cb3da648cdb24566987b0b59c5ebb80ab770748d6", + "sha256:727b5f2bed08552d143fc99649b1863c773729f580a416844f9d9967bb0a1ae8", + "sha256:74c24a3ec0a3d4f8acb13a07192f45bdb54a1cc3c2286241677e7e8bcd5011fa", + "sha256:98ccd2f8b4f8636db05f3f14db0b471ad6bb4b66ae0dc9052c4822b3bd5d6a7d", + "sha256:a5905aa567946bc938b489a7249c7890c3fd3c9b7b5680dece5bc551c2ddbe0d", + "sha256:acbb7f676b8586b770719e9683eda951fdb38eb7970d46fcbf3cdda88d912a64", + "sha256:b5e30f865add48cf880f1c363eb505b97f2f7baaa88c155f87a335a76515a3e5", + "sha256:be2a7c33a2c28bbd3f453ffe4f0e5200b88c803a097f4cf52d69c6b53fad7a8f", + "sha256:c356bb600f59ac64955813d6497a08bfd5d0c451cb5829b61e3913d0ac084e26", + "sha256:c7ec4ae2393beab584921b1287a04e94fd98c28315e348362d89b85f4b464546", + "sha256:d476edc831bb3e9ebd04d1403abaf3ea57b3e4c2276c91a54fdfb6efbd3f9d97", + "sha256:db059e1a0691c872784062421ec51848539eb4f5210142682e61059a5ca7cc55", + "sha256:dd423a6753509ab14a0ac1b5be39d219c8f8d3781cce3deb4f45eda31969b5e8", + "sha256:ed9b7c0d71ce6fe2b31c6cde530ad8238632b876a5d599218739bda142a77f7c", + "sha256:f0a2465af4006f97b05e1f1546d67d3a3213d414894bf28be7f87f550a7f4a55", + "sha256:f20bfe26e57e8e1f575a9e0325be04dd3562db9f247ffdd73b5d4df6dec53bc2", + "sha256:f3463f2cb40a1b515ac0133ba859eca58f53b56760da9abb27ed684c565f853c", + "sha256:facc3c9748ab1525fb8401a1223bce4f24f0d6aa1a9db86c55db75777ccf40f9" + ], + "version": "==3.5.13" }, "requests": { "hashes": [ @@ -422,10 +461,10 @@ }, "shodan": { "hashes": [ - "sha256:c40abb6ff2fd66bdee9f773746fb961eefdfaa8e720a07cb12fb70def136268d" + "sha256:f93b7199e89eecf5c84647f66316c2c044c3aebfc1fe4d9caa43dfda07f74c4e" ], "index": "pypi", - "version": "==1.10.4" + "version": "==1.11.1" }, "sigmatools": { "hashes": [ @@ -443,10 +482,10 @@ }, "soupsieve": { "hashes": [ - "sha256:466910df7561796a60748826781ebe9a888f7a1668a636ae86783f44d10aae73", - "sha256:87db12ae79194f0ff9808d2b1641c4f031ae39ffa3cab6b907ea7c1e5e5ed445" + "sha256:afa56bf14907bb09403e5d15fbed6275caa4174d36b975226e3b67a3bb6e2c4b", + "sha256:eaed742b48b1f3e2d45ba6f79401b2ed5dc33b2123dfe216adb90d4bfa0ade26" ], - "version": "==1.7.3" + "version": "==1.8" }, "sparqlwrapper": { "hashes": [ @@ -500,49 +539,48 @@ "uwhois": { "editable": true, "git": "https://github.com/Rafiot/uwhoisd.git", - "ref": "f6f035e52213c8abc20f2084d28cfffb399457cb", + "ref": "411572840eba4c72dc321c549b36a54ed5cea9de", "subdirectory": "client" }, "vulners": { "hashes": [ - "sha256:5f05404041cfaa8e5367bf884fc9ee319ebf34bedc495d7f84c433fa121cdb49", - "sha256:919b24df64ea55b6a8ba13e2a0530578f8a4be6a9cee257bf2214046e81c6f35", - "sha256:d45ecb13f5111947056a2dcc071b3e3fd45f6ad654eda06526245bba3850325e" + "sha256:40041bcf893fa1bfaf29c650369d9a249991911f28b4d8795f7bc06508013e14", + "sha256:6d00709300dcc7e2727499d8a60f51eaced1dc6b63cc19cb8a4b065b658c51aa", + "sha256:de8cef247c9852c39bd54434e63026b46bdb2bd4ca22813bf66626b7d359b0f3" ], "index": "pypi", - "version": "==1.4.0" + "version": "==1.4.4" }, "wand": { "hashes": [ - "sha256:3e59e4bda9ef9d643d90e881cc950c8eee1508ec2cde1c150a1cbd5a12c1c007", - "sha256:52763dbf65d00cf98d7bc910b49329eea15896249c5555d47e169f2b6efbe166" + "sha256:7d6b8dc9d4eaccc430b9c86e6b749013220c994970a3f39e902b397e2fa732c3", + "sha256:cc0b5c9cd50fecd10dc8888b739dd5984c6f8085d2954f34903b83ca39a91236" ], "index": "pypi", - "version": "==0.5.0" + "version": "==0.5.1" }, "xlsxwriter": { "hashes": [ - "sha256:7cc07619760641b67112dbe0df938399d4d915d9b9924bb58eb5c17384d29cc6", - "sha256:ae22658a0fc5b9e875fa97c213d1ffd617d86dc49bf08be99ebdac814db7bf36" + "sha256:de9ef46088489915eaaee00c7088cff93cf613e9990b46b933c98eb46f21b47f", + "sha256:df96eafc3136d9e790e35d6725b473e46ada6f585c1f6519da69b27f5c8873f7" ], - "version": "==1.1.2" + "version": "==1.1.5" }, "yara-python": { "hashes": [ - "sha256:03e5c5e333c8572e7994b0b11964d515d61a393f23c5e272f8d0e4229f368c58", - "sha256:0423e08bd618752a028ac0405ff8e0103f3a8fd607dde7618a64a4c010c3757b", - "sha256:0a0dd632dcdb347d1a9a8b1f6a83b3a77d5e63f691357ea4021fb1cf1d7ff0a4", - "sha256:728b99627a8072a877eaaa4dafb4eff39d1b14ff4fd70d39f18899ce81e29625", - "sha256:7cb0d5724eccfa52e1bcd352a56cb4dc422aa51f5f6d0945d4f830783927513b", - "sha256:8c76531e89806c0309586dd4863a972d12f1d5d63261c6d4b9331a99859fd1d8", - "sha256:9472676583e212bc4e17c2236634e02273d53c872b350f0571b48e06183de233", - "sha256:9735b680a7d95c1d3f255c351bb067edc62cdb3c0999f7064278cb2c85245405", - "sha256:997f104590167220a9af5564c042ec4d6534261e7b8a5b49655d8dffecc6b8a2", - "sha256:a48e071d02a3699363e628ac899b5b7237803bcb4b512c92ebcb4fb9b1488497", - "sha256:b67c0d75a6519ca357b4b85ede9768c96a81fff20fbc169bd805ff009ddee561" + "sha256:0d002170b2f2c56ff75c846ad1e6765f59d4569e81494c76f15243197e4a974c", + "sha256:16be7c7623685b4b2813db33a39553d6faef236ddffa0758c08e2071ab11ed84", + "sha256:2031ac6ac01754dbc82b5a47b69cb91302c6b66ea9d9f2f27cc2eaf771e19c14", + "sha256:228a96efc86c766d968c984bd80f5ebb0bb775afb9045c10fb632e2b7275c9c1", + "sha256:468a9770e6b578f0562a540b6cb5cafd4122bea989404b53440d4eb065d54eda", + "sha256:752d12a795159b806cd74ab7f0fd7c3a14cb6e17c9e4a818511dc7a4932b15df", + "sha256:755406cb5fa944d5e0dd097a4b25c3fcdd5ba244f0367114afed1ba30ccd2a12", + "sha256:7936c10c8802fc279802dcdda8270d3fda5c3d3c8fbe6bb02010934ed30b8929", + "sha256:95c8d39ee5938744dbd8e0153ec6d466f8a4ed11b8ac7b1068f498c26a292b65", + "sha256:cfd00cfb7bcbe862b0793f91b5393bad3fb37da78883af19924059367ba80f51" ], "index": "pypi", - "version": "==3.8.1" + "version": "==3.9.0" }, "yarl": { "hashes": [ @@ -643,11 +681,11 @@ }, "flake8": { "hashes": [ - "sha256:09b9bb539920776da542e67a570a5df96ff933c9a08b62cfae920bcc789e4383", - "sha256:e0f8cd519cfc0072c0ee31add5def09d2b3ef6040b34dc426445c3af9b02163c" + "sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661", + "sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8" ], "index": "pypi", - "version": "==3.7.4" + "version": "==3.7.7" }, "idna": { "hashes": [ @@ -665,11 +703,11 @@ }, "more-itertools": { "hashes": [ - "sha256:38a936c0a6d98a38bcc2d03fdaaedaba9f412879461dd2ceff8d37564d6522e4", - "sha256:c0a5785b1109a6bd7fac76d6837fd1feca158e54e521ccd2ae8bfe393cc9d4fc", - "sha256:fe7a7cae1ccb57d33952113ff4fa1bc5f879963600ed74918f1236e212ee50b9" + "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40", + "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1" ], - "version": "==5.0.0" + "markers": "python_version > '2.7'", + "version": "==6.0.0" }, "nose": { "hashes": [ @@ -682,17 +720,17 @@ }, "pluggy": { "hashes": [ - "sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616", - "sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a" + "sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f", + "sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746" ], - "version": "==0.8.1" + "version": "==0.9.0" }, "py": { "hashes": [ - "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", - "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" ], - "version": "==1.7.0" + "version": "==1.8.0" }, "pycodestyle": { "hashes": [ @@ -710,11 +748,11 @@ }, "pytest": { "hashes": [ - "sha256:65aeaa77ae87c7fc95de56285282546cfa9c886dc8e5dc78313db1c25e21bc07", - "sha256:6ac6d467d9f053e95aaacd79f831dbecfe730f419c6c7022cb316b365cd9199d" + "sha256:067a1d4bf827ffdd56ad21bd46674703fce77c5957f6c1eef731f6146bfcef1c", + "sha256:9687049d53695ad45cf5fdc7bbd51f0c49f1ea3ecfc4b7f3fde7501b541f17f4" ], "index": "pypi", - "version": "==4.2.0" + "version": "==4.3.0" }, "requests": { "hashes": [ From 637d7f25381b33f30a947cc23c50325f088e21cc Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 25 Feb 2019 20:42:45 +0100 Subject: [PATCH 329/401] chg: [requirements] reportlab added --- REQUIREMENTS | 1 + 1 file changed, 1 insertion(+) diff --git a/REQUIREMENTS b/REQUIREMENTS index 69b0568..4891c60 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -47,6 +47,7 @@ rdflib==4.2.2 redis==3.1.0 requests-cache==0.4.13 requests==2.21.0 +reportlab shodan==1.10.4 sigmatools==0.7.1 six==1.12.0 From b0ea67e393f91aefbf770123ce6f4cd0699d0e5e Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 25 Feb 2019 21:11:24 +0100 Subject: [PATCH 330/401] chg: [pipenv] fix the temporary issue that python-yara is not officially released --- Pipfile | 3 ++- Pipfile.lock | 26 ++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Pipfile b/Pipfile index c086e62..45c05f5 100644 --- a/Pipfile +++ b/Pipfile @@ -25,12 +25,13 @@ pytesseract = "*" pygeoip = "*" beautifulsoup4 = "*" oauth2 = "*" -yara-python = ">=3.8.0" +yara-python = "==3.8.1" sigmatools = "*" stix2-patterns = "*" maclookup = "*" vulners = "*" blockchain = "*" +reportlab = "*" pyintel471 = {editable = true,git = "https://github.com/MISP/PyIntel471.git"} shodan = "*" Pillow = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 1c08572..9e6265d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f501a84bdd41ca21a2af020278ce030985cccd5f2f5683cd075797be4523587d" + "sha256": "d0cd64bfe7702365d3ea66d1f51a1ec8592df2490899e7e163fe38f97172561e" }, "pipfile-spec": 6, "requires": { @@ -442,6 +442,7 @@ "sha256:f3463f2cb40a1b515ac0133ba859eca58f53b56760da9abb27ed684c565f853c", "sha256:facc3c9748ab1525fb8401a1223bce4f24f0d6aa1a9db86c55db75777ccf40f9" ], + "index": "pypi", "version": "==3.5.13" }, "requests": { @@ -568,19 +569,20 @@ }, "yara-python": { "hashes": [ - "sha256:0d002170b2f2c56ff75c846ad1e6765f59d4569e81494c76f15243197e4a974c", - "sha256:16be7c7623685b4b2813db33a39553d6faef236ddffa0758c08e2071ab11ed84", - "sha256:2031ac6ac01754dbc82b5a47b69cb91302c6b66ea9d9f2f27cc2eaf771e19c14", - "sha256:228a96efc86c766d968c984bd80f5ebb0bb775afb9045c10fb632e2b7275c9c1", - "sha256:468a9770e6b578f0562a540b6cb5cafd4122bea989404b53440d4eb065d54eda", - "sha256:752d12a795159b806cd74ab7f0fd7c3a14cb6e17c9e4a818511dc7a4932b15df", - "sha256:755406cb5fa944d5e0dd097a4b25c3fcdd5ba244f0367114afed1ba30ccd2a12", - "sha256:7936c10c8802fc279802dcdda8270d3fda5c3d3c8fbe6bb02010934ed30b8929", - "sha256:95c8d39ee5938744dbd8e0153ec6d466f8a4ed11b8ac7b1068f498c26a292b65", - "sha256:cfd00cfb7bcbe862b0793f91b5393bad3fb37da78883af19924059367ba80f51" + "sha256:03e5c5e333c8572e7994b0b11964d515d61a393f23c5e272f8d0e4229f368c58", + "sha256:0423e08bd618752a028ac0405ff8e0103f3a8fd607dde7618a64a4c010c3757b", + "sha256:0a0dd632dcdb347d1a9a8b1f6a83b3a77d5e63f691357ea4021fb1cf1d7ff0a4", + "sha256:728b99627a8072a877eaaa4dafb4eff39d1b14ff4fd70d39f18899ce81e29625", + "sha256:7cb0d5724eccfa52e1bcd352a56cb4dc422aa51f5f6d0945d4f830783927513b", + "sha256:8c76531e89806c0309586dd4863a972d12f1d5d63261c6d4b9331a99859fd1d8", + "sha256:9472676583e212bc4e17c2236634e02273d53c872b350f0571b48e06183de233", + "sha256:9735b680a7d95c1d3f255c351bb067edc62cdb3c0999f7064278cb2c85245405", + "sha256:997f104590167220a9af5564c042ec4d6534261e7b8a5b49655d8dffecc6b8a2", + "sha256:a48e071d02a3699363e628ac899b5b7237803bcb4b512c92ebcb4fb9b1488497", + "sha256:b67c0d75a6519ca357b4b85ede9768c96a81fff20fbc169bd805ff009ddee561" ], "index": "pypi", - "version": "==3.9.0" + "version": "==3.8.1" }, "yarl": { "hashes": [ From e7fd7e8eb20ed92ab5b09e83d7acf004fa366b6f Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 25 Feb 2019 21:18:26 +0100 Subject: [PATCH 331/401] chg: [pdfexport] make flake8 happy --- misp_modules/modules/export_mod/pdfexport.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 977ee87..6b0c12f 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -1,11 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from datetime import date import json -import shlex -import subprocess -import base64 from pymisp import MISPEvent from pymisp.tools import reportlab_generator @@ -46,6 +42,7 @@ class ReportGenerator(): self.misp_event = MISPEvent() self.misp_event.load(event) + def handler(q=False): if q is False: return False @@ -58,12 +55,11 @@ def handler(q=False): config = {} # Construct config object for reportlab_generator - for config_item in moduleconfig : + for config_item in moduleconfig: if (request.get('config')) and (request['config'].get(config_item) is not None): config[config_item] = request['config'].get(config_item) for evt in request['data']: - misp_event = MISPEvent() misp_event.load(evt) From 2a59c6becc3e24c56febe838adbcc965e929d49b Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 25 Feb 2019 21:33:47 +0100 Subject: [PATCH 332/401] chg: [doc] PDF export --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee4f2f8..501e54f 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [CEF](misp_modules/modules/export_mod/cef_export.py) module to export Common Event Format (CEF). * [GoAML export](misp_modules/modules/export_mod/goamlexport.py) module to export in [GoAML format](http://goaml.unodc.org/goaml/en/index.html). * [Lite Export](misp_modules/modules/export_mod/liteexport.py) module to export a lite event. -* [Simple PDF export](misp_modules/modules/export_mod/pdfexport.py) module to export in PDF (required: asciidoctor-pdf). +* [PDF export](misp_modules/modules/export_mod/pdfexport.py) module to export an event in PDF. * [Nexthink query format](misp_modules/modules/export_mod/nexthinkexport.py) module to export in Nexthink query format. * [osquery](misp_modules/modules/export_mod/osqueryexport.py) module to export in [osquery](https://osquery.io/) query format. * [ThreatConnect](misp_modules/modules/export_mod/threat_connect_export.py) module to export in ThreatConnect CSV format. From a770bfc5934157f895f211539911f6dcd7481e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 26 Feb 2019 16:43:08 -0800 Subject: [PATCH 333/401] chg: Bump dependencies, add update script --- Pipfile.lock | 2 +- REQUIREMENTS | 32 ++++++++++++++++---------------- setup.py | 1 + tools/update_misp_modules.sh | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 17 deletions(-) create mode 100755 tools/update_misp_modules.sh diff --git a/Pipfile.lock b/Pipfile.lock index 9e6265d..85cd9db 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -333,7 +333,7 @@ "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "634ecc3ac308d01ebf5f5fbb9aace7746a2b8707" + "ref": "62e047f3c1972e21aa36a8882bebf4488cdc1f84" }, "pyonyphe": { "editable": true, diff --git a/REQUIREMENTS b/REQUIREMENTS index 4891c60..4709747 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -1,17 +1,16 @@ -i https://pypi.org/simple -e . --e git+https://github.com/D4-project/BGP-Ranking.git/@7e698f87366e6f99b4d0d11852737db28e3ddc62#egg=pybgpranking&subdirectory=client +-e git+https://github.com/D4-project/BGP-Ranking.git/@37c97ae252ec4bf1d67733a49d4895c8cb009cf9#egg=pybgpranking&subdirectory=client -e git+https://github.com/D4-project/IPASN-History.git/@e846cd36fe1ed6b22f60890bba89f84e61b62e59#egg=pyipasnhistory&subdirectory=client -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 --e git+https://github.com/MISP/PyMISP.git@634ecc3ac308d01ebf5f5fbb9aace7746a2b8707#egg=pymisp --e git+https://github.com/Rafiot/uwhoisd.git@f6f035e52213c8abc20f2084d28cfffb399457cb#egg=uwhois&subdirectory=client +-e git+https://github.com/MISP/PyMISP.git@62e047f3c1972e21aa36a8882bebf4488cdc1f84#egg=pymisp +-e git+https://github.com/Rafiot/uwhoisd.git@411572840eba4c72dc321c549b36a54ed5cea9de#egg=uwhois&subdirectory=client -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails --e git+https://github.com/sebdraven/pyonyphe@66329baeee7cab844f2203c047c2551828eaf14d#egg=pyonyphe +-e git+https://github.com/sebdraven/pyonyphe@cbb0168d5cb28a9f71f7ab3773164a7039ccdb12#egg=pyonyphe aiohttp==3.4.4 antlr4-python3-runtime==4.7.2 ; python_version >= '3' async-timeout==3.0.1 attrs==18.2.0 -backscatter==0.2.3 beautifulsoup4==4.7.1 blockchain==1.4.4 certifi==2018.11.29 @@ -24,42 +23,43 @@ domaintools-api==0.3.3 enum-compat==0.0.2 ez-setup==0.9 future==0.17.1 -httplib2==0.12.0 +httplib2==0.12.1 idna-ssl==1.1.0 ; python_version < '3.7' idna==2.8 isodate==0.6.0 -jsonschema==2.6.0 +jsonschema==3.0.0 maclookup==1.0.3 multidict==4.5.2 oauth2==1.9.0.post1 passivetotal==1.0.30 pillow==5.4.1 -psutil==5.5.0 +psutil==5.5.1 pyeupi==1.0 pygeoip==0.3.2 pyparsing==2.3.1 pypdns==1.3 pypssl==2.1 +pyrsistent==0.14.11 pytesseract==0.2.6 -python-dateutil==2.7.5 +python-dateutil==2.8.0 pyyaml==3.13 rdflib==4.2.2 -redis==3.1.0 +redis==3.2.0 +reportlab==3.5.13 requests-cache==0.4.13 requests==2.21.0 -reportlab -shodan==1.10.4 +shodan==1.11.1 sigmatools==0.7.1 six==1.12.0 -soupsieve==1.7.3 +soupsieve==1.8 sparqlwrapper==1.8.2 stix2-patterns==1.1.0 tornado==5.1.1 url-normalize==1.4.1 urlarchiver==0.2 urllib3==1.24.1 -vulners==1.4.0 -wand==0.5.0 -xlsxwriter==1.1.2 +vulners==1.4.4 +wand==0.5.1 +xlsxwriter==1.1.5 yara-python==3.8.1 yarl==1.3.0 diff --git a/setup.py b/setup.py index fc78750..55ed8b7 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ setup( description='MISP modules are autonomous modules that can be used for expansion and other services in MISP', packages=find_packages(), entry_points={'console_scripts': ['misp-modules = misp_modules:main']}, + scripts=['tools/update_misp_modules.sh'], test_suite="tests", classifiers=[ 'License :: OSI Approved :: GNU Affero General Public License v3', diff --git a/tools/update_misp_modules.sh b/tools/update_misp_modules.sh new file mode 100755 index 0000000..e0578fd --- /dev/null +++ b/tools/update_misp_modules.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -e +set -x + +# Updates the MISP Modules while respecting the current permissions +# It aims to support the two following installation methods: +# * Everything is runinng on the same machine following the MISP installation guide. +# * The modules are installed using pipenv on a different machine from the one where MISP is running. + +if [ -d "/var/www/MISP" ] && [ -d "/usr/local/src/misp-modules" ] +then + echo "MISP is installed on the same machine, following the recommanded install script. Using MISP virtualenv." + PATH_TO_MISP="/var/www/MISP" + PATH_TO_MISP_MODULES="/usr/local/src/misp-modules" + + pushd ${PATH_TO_MISP_MODULES} + USER=`stat -c "%U" .` + sudo -H -u ${USER} git pull + sudo -H -u ${USER} ${PATH_TO_MISP}/venv/bin/pip install -U -r REQUIREMENTS + sudo -H -u ${USER} ${PATH_TO_MISP}/venv/bin/pip install -U -e . + + popd +else + if ! [ -x "$(command -v pipenv)" ]; then + echo 'Error: pipenv not available, unable to automatically update.' >&2 + exit 1 + fi + + echo "Standalone mode, use pipenv from the current directory." + git pull + pipenv install +fi + + From 75953c32a7c093401a88e0f05d2447c9f4dbbac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 26 Feb 2019 16:48:11 -0800 Subject: [PATCH 334/401] chr: Restart the modules after update --- tools/update_misp_modules.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/update_misp_modules.sh b/tools/update_misp_modules.sh index e0578fd..372d146 100755 --- a/tools/update_misp_modules.sh +++ b/tools/update_misp_modules.sh @@ -20,6 +20,8 @@ then sudo -H -u ${USER} ${PATH_TO_MISP}/venv/bin/pip install -U -r REQUIREMENTS sudo -H -u ${USER} ${PATH_TO_MISP}/venv/bin/pip install -U -e . + service misp-modules restart + popd else if ! [ -x "$(command -v pipenv)" ]; then From a937b7c85dae6a22f7f3d5373e628e9f36aae384 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Wed, 27 Feb 2019 12:45:22 +0100 Subject: [PATCH 335/401] fix: [reportlab] Textual description parameter --- misp_modules/modules/export_mod/pdfexport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 6b0c12f..c143b5e 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -15,7 +15,7 @@ moduleinfo = {'version': '2', 'require_standard_format': True} # config fields that your code expects from the site admin -moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata"] +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description"] mispattributes = {} From a2716bc05d0c94a4c4d147591c2cb74a1a685882 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 09:11:34 +0100 Subject: [PATCH 336/401] fix: [exportpdf] add configmodule parameter for galaxy --- misp_modules/modules/export_mod/pdfexport.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index c143b5e..096992b 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -15,8 +15,8 @@ moduleinfo = {'version': '2', 'require_standard_format': True} # config fields that your code expects from the site admin -moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description"] - +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", + "Activate_galaxy_description"] mispattributes = {} outputFileExtension = "pdf" From aef8dbbe2eca411757bc3e56c6dd4040de0daec5 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 09:17:38 +0100 Subject: [PATCH 337/401] fix: [exportpdf] problem on one line --- misp_modules/modules/export_mod/pdfexport.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 096992b..d4e3be5 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -15,8 +15,7 @@ moduleinfo = {'version': '2', 'require_standard_format': True} # config fields that your code expects from the site admin -moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", - "Activate_galaxy_description"] +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description","Activate_galaxy_description"] mispattributes = {} outputFileExtension = "pdf" From 7d7c90143ef062cbb97e4f2baf88ea5e32632033 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Fri, 1 Mar 2019 09:25:02 +0100 Subject: [PATCH 338/401] fix: [exportpdf] mising whitespace --- misp_modules/modules/export_mod/pdfexport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index d4e3be5..7402e27 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -15,7 +15,7 @@ moduleinfo = {'version': '2', 'require_standard_format': True} # config fields that your code expects from the site admin -moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description","Activate_galaxy_description"] +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", "Activate_galaxy_description"] mispattributes = {} outputFileExtension = "pdf" From 3b415cb53a2eff78dc1d3d56264764a81de6c3a9 Mon Sep 17 00:00:00 2001 From: cgi1 Date: Fri, 1 Mar 2019 12:13:27 +0100 Subject: [PATCH 339/401] Adding virtualenv to apt-get install --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 501e54f..6ef4bf4 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ## How to install and start MISP modules in a Python virtualenv? ~~~~bash -sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick +sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick virtualenv sudo -u www-data virtualenv -p python3 /var/www/MISP/venv cd /usr/local/src/ sudo git clone https://github.com/MISP/misp-modules.git From a30bcc5dd20f44c19d867e5cb9328113ad8ea980 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Mon, 4 Mar 2019 12:36:18 +0100 Subject: [PATCH 340/401] fix: [exportpdf] add parameters --- misp_modules/modules/export_mod/pdfexport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index 7402e27..d12dece 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -15,7 +15,7 @@ moduleinfo = {'version': '2', 'require_standard_format': True} # config fields that your code expects from the site admin -moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", "Activate_galaxy_description"] +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", "Activate_galaxy_description", "Activate_related_events", "Activate_internationalization_fonts"] mispattributes = {} outputFileExtension = "pdf" From e3ddbe66a62830f69a47c228209dcef6f3a6c14e Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Mon, 4 Mar 2019 23:08:58 +0100 Subject: [PATCH 341/401] chg: [doc] asciidoctor requirement removed (new PDF module use reportlab) --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 6ef4bf4..951de64 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,6 @@ sudo git clone https://github.com/MISP/misp-modules.git cd misp-modules sudo -u www-data /var/www/MISP/venv/bin/pip install -I -r REQUIREMENTS sudo -u www-data /var/www/MISP/venv/bin/pip install . -sudo apt install ruby-pygments.rb -y -sudo gem install asciidoctor-pdf --pre sudo sed -i -e '$i \sudo -u www-data /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s > /tmp/misp-modules_rc.local.log &\n' /etc/rc.local /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules ~~~~ @@ -109,8 +107,6 @@ sudo git clone https://github.com/MISP/misp-modules.git cd misp-modules sudo pip3 install -I -r REQUIREMENTS sudo pip3 install -I . -sudo apt install ruby-pygments.rb -y -sudo gem install asciidoctor-pdf --pre sudo sed -i -e '$i \sudo -u www-data /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s > /tmp/misp-modules_rc.local.log &\n' /etc/rc.local /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules ~~~~ @@ -125,7 +121,6 @@ cd misp-modules scl enable rh-python36 ‘python3 –m pip install cryptography’ scl enable rh-python36 ‘python3 –m pip install -I -r REQUIREMENTS’ scl enable rh-python36 ‘python3 –m pip install –I .’ -scl enable rh-ruby22 ‘gem install asciidoctor-pdf –pre’ ~~~~ Create the service file /etc/systemd/system/misp-workers.service : ~~~~ From 32e10ee27333410a1ed4d2c4afe10d8c3b7456be Mon Sep 17 00:00:00 2001 From: Falconieri Date: Tue, 5 Mar 2019 10:39:07 +0100 Subject: [PATCH 342/401] fix: [exportpdf] custom path parameter --- misp_modules/modules/export_mod/pdfexport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misp_modules/modules/export_mod/pdfexport.py b/misp_modules/modules/export_mod/pdfexport.py index d12dece..44b3bc9 100755 --- a/misp_modules/modules/export_mod/pdfexport.py +++ b/misp_modules/modules/export_mod/pdfexport.py @@ -15,7 +15,7 @@ moduleinfo = {'version': '2', 'require_standard_format': True} # config fields that your code expects from the site admin -moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", "Activate_galaxy_description", "Activate_related_events", "Activate_internationalization_fonts"] +moduleconfig = ["MISP_base_url_for_dynamic_link", "MISP_name_for_metadata", "Activate_textual_description", "Activate_galaxy_description", "Activate_related_events", "Activate_internationalization_fonts", "Custom_fonts_path"] mispattributes = {} outputFileExtension = "pdf" From 30c08708c62a773d24de1b36308cdb613d0f55a5 Mon Sep 17 00:00:00 2001 From: Falconieri Date: Tue, 5 Mar 2019 12:11:44 +0100 Subject: [PATCH 343/401] fix: [exportpdf] update documentation --- doc/export_mod/pdfexport.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/export_mod/pdfexport.json b/doc/export_mod/pdfexport.json index 9803c77..f1654dc 100644 --- a/doc/export_mod/pdfexport.json +++ b/doc/export_mod/pdfexport.json @@ -1,7 +1,7 @@ { "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.", + "requirements": ["PyMISP", "reportlab"], + "features": "The module takes care of the PDF file building, and work with any MISP Event. Except the requirement of reportlab, used to create the file, there is no special feature concerning the Event. Some parameters can be given through the config dict. 'MISP_base_url_for_dynamic_link' is your MISP URL, to attach an hyperlink to your event on your MISP instance from the PDF. Keep it clear to avoid hyperlinks in the generated pdf.\n 'MISP_name_for_metadata' is your CERT or MISP instance name. Used as text in the PDF' metadata\n 'Activate_textual_description' is a boolean (True or void) to activate the textual description/header abstract of an event\n 'Activate_galaxy_description' is a boolean (True or void) to activate the description of event related galaxies.\n 'Activate_related_events' is a boolean (True or void) to activate the description of related event. Be aware this might leak information on confidential events linked to the current event !\n 'Activate_internationalization_fonts' is a boolean (True or void) to activate Noto fonts instead of default fonts (Helvetica). This allows the support of CJK alphabet. Be sure to have followed the procedure to download Noto fonts (~70Mo) in the right place (/tools/pdf_fonts/Noto_TTF), to allow PyMisp to find and use them during PDF generation.\n 'Custom_fonts_path' is a text (path or void) to the TTF file of your choice, to create the PDF with it. Be aware the PDF won't support bold/italic/special style anymore with this option ", "references": ["https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html"], "input": "MISP Event", "output": "MISP Event in a PDF file." From 9611c7f2a9a1bd84081ca3bc625f731d8185c842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Sat, 9 Mar 2019 06:15:16 +0100 Subject: [PATCH 344/401] chg: Bump Requirements --- Pipfile.lock | 74 ++++++++++++++++++++++++++-------------------------- REQUIREMENTS | 14 +++++----- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 85cd9db..36d7c3c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -59,10 +59,10 @@ }, "attrs": { "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" ], - "version": "==18.2.0" + "version": "==19.1.0" }, "beautifulsoup4": { "hashes": [ @@ -177,10 +177,10 @@ }, "jsonschema": { "hashes": [ - "sha256:acc8a90c31d11060516cfd0b414b9f8bcf4bc691b21f0f786ea57dd5255c79db", - "sha256:dd3f8ecb1b52d94d45eedb67cb86cac57b94ded562c5d98f63719e55ce58557b" + "sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d", + "sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a" ], - "version": "==3.0.0" + "version": "==3.0.1" }, "maclookup": { "hashes": [ @@ -281,17 +281,17 @@ }, "psutil": { "hashes": [ - "sha256:5ce6b5eb0267233459f4d3980c205828482f450999b8f5b684d9629fea98782a", - "sha256:72cebfaa422b7978a1d3632b65ff734a34c6b34f4578b68a5c204d633756b810", - "sha256:77c231b4dff8c1c329a4cd1c22b96c8976c597017ff5b09993cd148d6a94500c", - "sha256:8846ab0be0cdccd6cc92ecd1246a16e2f2e49f53bd73e522c3a75ac291e1b51d", - "sha256:a013b4250ccbddc9d22feca0f986a1afc71717ad026c0f2109bbffd007351191", - "sha256:ad43b83119eeea6d5751023298cd331637e542cbd332196464799e25a5519f8f", - "sha256:c177777c787d247d02dae6c855330f9ed3e1abf8ca1744c26dd5ff968949999a", - "sha256:ec1ef313530a9457e48d25e3fdb1723dfa636008bf1b970027462d46f2555d59", - "sha256:ef3e5e02b3c5d1df366abe7b4820400d5c427579668ad4465ff189d28ded5ebd" + "sha256:1020a37214c4138e34962881372b40f390582b5c8245680c04349c2afb785a25", + "sha256:151c9858c268a1523e16fab33e3bc3bae8a0e57b57cf7fcad85fb409cbac6baf", + "sha256:1c8e6444ca1cee9a60a1a35913b8409722f7474616e0e21004e4ffadba59964b", + "sha256:722dc0dcce5272f3c5c41609fdc2c8f0ee3f976550c2d2f2057e26ba760be9c0", + "sha256:86f61a1438c026c980a4c3e2dd88a5774a3a0f00d6d0954d6c5cf8d1921b804e", + "sha256:c4a2f42abee709ed97b4498c21aa608ac31fc1f7cc8aa60ebdcd3c80757a038d", + "sha256:d9cdc2e82aeb82200fff3640f375fac39d88b1bed27ce08377cd7fb0e3621cb7", + "sha256:da6676a484adec2fdd3e1ce1b70799881ffcb958e40208dd4c5beba0011f3589", + "sha256:dca71c08335fbfc6929438fe3a502f169ba96dd20e50b3544053d6be5cb19d82" ], - "version": "==5.5.1" + "version": "==5.6.0" }, "pybgpranking": { "editable": true, @@ -333,7 +333,7 @@ "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "62e047f3c1972e21aa36a8882bebf4488cdc1f84" + "ref": "b8759673b91e733c307698abdc0d5ed82fd7e0de" }, "pyonyphe": { "editable": true, @@ -469,10 +469,10 @@ }, "sigmatools": { "hashes": [ - "sha256:98c9897f27e7c99f398bff537bb6b0259599177d955f8b60a22db1b246f9cb0b" + "sha256:3bdbd2ee99c32f245e948d6b882219729ab379685dd7366e4d6149c390e08170" ], "index": "pypi", - "version": "==0.7.1" + "version": "==0.9" }, "six": { "hashes": [ @@ -506,15 +506,15 @@ }, "tornado": { "hashes": [ - "sha256:0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d", - "sha256:4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409", - "sha256:732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f", - "sha256:8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f", - "sha256:8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5", - "sha256:d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb", - "sha256:e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444" + "sha256:1a58f2d603476d5e462f7c28ca1dbb5ac7e51348b27a9cac849cdec3471101f8", + "sha256:33f93243cd46dd398e5d2bbdd75539564d1f13f25d704cfc7541db74066d6695", + "sha256:34e59401afcecf0381a28228daad8ed3275bcb726810654612d5e9c001f421b7", + "sha256:35817031611d2c296c69e5023ea1f9b5720be803e3bb119464bb2a0405d5cd70", + "sha256:666b335cef5cc2759c21b7394cff881f71559aaf7cb8c4458af5bb6cb7275b47", + "sha256:81203efb26debaaef7158187af45bc440796de9fb1df12a75b65fae11600a255", + "sha256:de274c65f45f6656c375cdf1759dbf0bc52902a1e999d12a35eb13020a641a53" ], - "version": "==5.1.1" + "version": "==6.0.1" }, "url-normalize": { "hashes": [ @@ -545,12 +545,12 @@ }, "vulners": { "hashes": [ - "sha256:40041bcf893fa1bfaf29c650369d9a249991911f28b4d8795f7bc06508013e14", - "sha256:6d00709300dcc7e2727499d8a60f51eaced1dc6b63cc19cb8a4b065b658c51aa", - "sha256:de8cef247c9852c39bd54434e63026b46bdb2bd4ca22813bf66626b7d359b0f3" + "sha256:08a7ccb2b210d45143354c6161c73fe209dc14fae8692e8b793b36b79330ad11", + "sha256:bfe2478cc11c69ba7e436d7a5df925e227565782c0bd603929fb3d612c73d78d", + "sha256:d035f6a883625878a1dc377830d17d9702ef138ca31569ac01cb8686874f89cd" ], "index": "pypi", - "version": "==1.4.4" + "version": "==1.4.5" }, "wand": { "hashes": [ @@ -611,10 +611,10 @@ }, "attrs": { "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" ], - "version": "==18.2.0" + "version": "==19.1.0" }, "certifi": { "hashes": [ @@ -743,10 +743,10 @@ }, "pyflakes": { "hashes": [ - "sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d", - "sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd" + "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", + "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" ], - "version": "==2.1.0" + "version": "==2.1.1" }, "pytest": { "hashes": [ diff --git a/REQUIREMENTS b/REQUIREMENTS index 4709747..d672411 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -3,14 +3,14 @@ -e git+https://github.com/D4-project/BGP-Ranking.git/@37c97ae252ec4bf1d67733a49d4895c8cb009cf9#egg=pybgpranking&subdirectory=client -e git+https://github.com/D4-project/IPASN-History.git/@e846cd36fe1ed6b22f60890bba89f84e61b62e59#egg=pyipasnhistory&subdirectory=client -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 --e git+https://github.com/MISP/PyMISP.git@62e047f3c1972e21aa36a8882bebf4488cdc1f84#egg=pymisp +-e git+https://github.com/MISP/PyMISP.git@b8759673b91e733c307698abdc0d5ed82fd7e0de#egg=pymisp -e git+https://github.com/Rafiot/uwhoisd.git@411572840eba4c72dc321c549b36a54ed5cea9de#egg=uwhois&subdirectory=client -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails -e git+https://github.com/sebdraven/pyonyphe@cbb0168d5cb28a9f71f7ab3773164a7039ccdb12#egg=pyonyphe aiohttp==3.4.4 antlr4-python3-runtime==4.7.2 ; python_version >= '3' async-timeout==3.0.1 -attrs==18.2.0 +attrs==19.1.0 beautifulsoup4==4.7.1 blockchain==1.4.4 certifi==2018.11.29 @@ -27,13 +27,13 @@ httplib2==0.12.1 idna-ssl==1.1.0 ; python_version < '3.7' idna==2.8 isodate==0.6.0 -jsonschema==3.0.0 +jsonschema==3.0.1 maclookup==1.0.3 multidict==4.5.2 oauth2==1.9.0.post1 passivetotal==1.0.30 pillow==5.4.1 -psutil==5.5.1 +psutil==5.6.0 pyeupi==1.0 pygeoip==0.3.2 pyparsing==2.3.1 @@ -49,16 +49,16 @@ reportlab==3.5.13 requests-cache==0.4.13 requests==2.21.0 shodan==1.11.1 -sigmatools==0.7.1 +sigmatools==0.9 six==1.12.0 soupsieve==1.8 sparqlwrapper==1.8.2 stix2-patterns==1.1.0 -tornado==5.1.1 +tornado==6.0.1 url-normalize==1.4.1 urlarchiver==0.2 urllib3==1.24.1 -vulners==1.4.4 +vulners==1.4.5 wand==0.5.1 xlsxwriter==1.1.5 yara-python==3.8.1 From c4ced9dfbf49ec605f230b35564e17a94bcea6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Sat, 9 Mar 2019 06:40:23 +0100 Subject: [PATCH 345/401] fix: Tornado expects a KILL now. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b574d4c..e8fea8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,14 +19,14 @@ script: - pid=$! - sleep 5 - pipenv run nosetests --with-coverage --cover-package=misp_modules - - kill -s INT $pid + - kill -s KILL $pid - pushd ~/ - pipenv run coverage run -m --parallel-mode --source=misp_modules misp_modules.__init__ -s -l 127.0.0.1 & - pid=$! - popd - sleep 5 - pipenv run nosetests --with-coverage --cover-package=misp_modules - - kill -s INT $pid + - kill -s KILL $pid - pipenv run flake8 --ignore=E501,W503 misp_modules after_success: From 4b77cb5055e0a114c5bf361cadefc4aacbe0ae39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Sun, 10 Mar 2019 21:17:30 +0100 Subject: [PATCH 346/401] new: Add missing dependency (backscatter) --- Pipfile | 1 + Pipfile.lock | 92 ++++++++++++++++++++++++++++++---------------------- REQUIREMENTS | 4 ++- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/Pipfile b/Pipfile index 45c05f5..2f2d172 100644 --- a/Pipfile +++ b/Pipfile @@ -41,6 +41,7 @@ domaintools_api = "*" misp-modules = {editable = true,path = "."} pybgpranking = {editable = true,git = "https://github.com/D4-project/BGP-Ranking.git/",subdirectory = "client"} pyipasnhistory = {editable = true,git = "https://github.com/D4-project/IPASN-History.git/",subdirectory = "client"} +backscatter = "*" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 36d7c3c..3c902e7 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d0cd64bfe7702365d3ea66d1f51a1ec8592df2490899e7e163fe38f97172561e" + "sha256": "23dec0fa6400c828e294ea9981b433903c17358ca61d7abdaec8df5a1c89f08c" }, "pipfile-spec": 6, "requires": { @@ -64,6 +64,14 @@ ], "version": "==19.1.0" }, + "backscatter": { + "hashes": [ + "sha256:7a0d1aa3661635de81e2a09b15d53e35cbe399a111cc58a70925f80e6874abd3", + "sha256:afb0efcf5d2551dac953ec4c38fb710b274b8e811775650e02c1ef42cafb14c8" + ], + "index": "pypi", + "version": "==0.2.4" + }, "beautifulsoup4": { "hashes": [ "sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858", @@ -82,10 +90,10 @@ }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" ], - "version": "==2018.11.29" + "version": "==2019.3.9" }, "chardet": { "hashes": [ @@ -504,6 +512,12 @@ "index": "pypi", "version": "==1.1.0" }, + "tabulate": { + "hashes": [ + "sha256:8af07a39377cee1103a5c8b3330a421c2d99b9141e9cc5ddd2e3263fea416943" + ], + "version": "==0.8.3" + }, "tornado": { "hashes": [ "sha256:1a58f2d603476d5e462f7c28ca1dbb5ac7e51348b27a9cac849cdec3471101f8", @@ -618,10 +632,10 @@ }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" ], - "version": "==2018.11.29" + "version": "==2019.3.9" }, "chardet": { "hashes": [ @@ -640,39 +654,39 @@ }, "coverage": { "hashes": [ - "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f", - "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe", - "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d", - "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0", - "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607", - "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d", - "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b", - "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3", - "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e", - "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815", - "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36", - "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1", - "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14", - "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c", - "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794", - "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b", - "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840", - "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd", - "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82", - "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952", - "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389", - "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f", - "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4", - "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da", - "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647", - "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d", - "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42", - "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478", - "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b", - "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb", - "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9" + "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", + "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", + "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", + "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", + "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", + "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", + "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", + "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", + "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", + "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", + "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", + "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", + "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", + "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", + "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", + "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", + "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", + "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", + "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", + "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", + "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", + "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", + "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", + "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", + "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", + "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", + "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", + "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", + "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", + "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", + "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" ], - "version": "==4.5.2" + "version": "==4.5.3" }, "entrypoints": { "hashes": [ diff --git a/REQUIREMENTS b/REQUIREMENTS index d672411..99e1c02 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -11,9 +11,10 @@ aiohttp==3.4.4 antlr4-python3-runtime==4.7.2 ; python_version >= '3' async-timeout==3.0.1 attrs==19.1.0 +backscatter==0.2.4 beautifulsoup4==4.7.1 blockchain==1.4.4 -certifi==2018.11.29 +certifi==2019.3.9 chardet==3.0.4 click-plugins==1.0.4 click==7.0 @@ -54,6 +55,7 @@ six==1.12.0 soupsieve==1.8 sparqlwrapper==1.8.2 stix2-patterns==1.1.0 +tabulate==0.8.3 tornado==6.0.1 url-normalize==1.4.1 urlarchiver==0.2 From eb2dcca12b853ead10856b7de15b4d0fff4209f2 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Thu, 14 Mar 2019 14:39:58 +0100 Subject: [PATCH 347/401] fixed a bug when checking malformed BTC addresses --- misp_modules/modules/expansion/btc_steroids.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index 7011eda..5958502 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -91,6 +91,7 @@ def mprint(input): def handler(q=False): global result_text global conversion_rates + result_text = "" # start_time = time.time() # now = time.time() if q is False: @@ -105,7 +106,6 @@ def handler(q=False): btc = request['btc'] else: return False - mprint("\nAddress:\t" + btc) try: req = requests.get(blockchain_all.format(btc, "&limit=50")) @@ -113,8 +113,18 @@ def handler(q=False): except Exception: # print(e) print(req.text) - result_text = "" - sys.exit(1) + result_text = "Not a valid BTC address" + r = { + 'results': [ + { + 'types': ['text'], + 'values':[ + str(result_text) + ] + } + ] + } + return r n_tx = jreq['n_tx'] balance = float(jreq['final_balance'] / 100000000) From 97818e17d009f1c20806476aff8d10becb36f30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D4=9C=D0=B5=D1=95?= <5124946+wesinator@users.noreply.github.com> Date: Thu, 14 Mar 2019 13:28:22 -0400 Subject: [PATCH 348/401] Fix command highlighting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 951de64..e890433 100644 --- a/README.md +++ b/README.md @@ -505,14 +505,14 @@ sudo git checkout MyModBranch Remove the contents of the build directory and re-install misp-modules. -~~~python +~~~bash sudo rm -fr build/* sudo pip3 install --upgrade . ~~~ SSH in with a different terminal and run `misp-modules` with debugging enabled. -~~~python +~~~bash sudo killall misp-modules misp-modules -d ~~~ From 1c0984eaec5145643933779601390e811fb9084d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 15 Mar 2019 11:06:11 +0100 Subject: [PATCH 349/401] fix: Remove unused import --- misp_modules/modules/expansion/btc_steroids.py | 1 - 1 file changed, 1 deletion(-) diff --git a/misp_modules/modules/expansion/btc_steroids.py b/misp_modules/modules/expansion/btc_steroids.py index 2f3bebf..04b7138 100755 --- a/misp_modules/modules/expansion/btc_steroids.py +++ b/misp_modules/modules/expansion/btc_steroids.py @@ -1,4 +1,3 @@ -import sys import json import requests import time From 3e34f38cac78d6e495be8dcf4eee4d4b7dcae181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 15 Mar 2019 11:12:19 +0100 Subject: [PATCH 350/401] chg: Bump dependencies. --- Pipfile.lock | 56 ++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 3c902e7..60f1d03 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -289,22 +289,22 @@ }, "psutil": { "hashes": [ - "sha256:1020a37214c4138e34962881372b40f390582b5c8245680c04349c2afb785a25", - "sha256:151c9858c268a1523e16fab33e3bc3bae8a0e57b57cf7fcad85fb409cbac6baf", - "sha256:1c8e6444ca1cee9a60a1a35913b8409722f7474616e0e21004e4ffadba59964b", - "sha256:722dc0dcce5272f3c5c41609fdc2c8f0ee3f976550c2d2f2057e26ba760be9c0", - "sha256:86f61a1438c026c980a4c3e2dd88a5774a3a0f00d6d0954d6c5cf8d1921b804e", - "sha256:c4a2f42abee709ed97b4498c21aa608ac31fc1f7cc8aa60ebdcd3c80757a038d", - "sha256:d9cdc2e82aeb82200fff3640f375fac39d88b1bed27ce08377cd7fb0e3621cb7", - "sha256:da6676a484adec2fdd3e1ce1b70799881ffcb958e40208dd4c5beba0011f3589", - "sha256:dca71c08335fbfc6929438fe3a502f169ba96dd20e50b3544053d6be5cb19d82" + "sha256:23e9cd90db94fbced5151eaaf9033ae9667c033dffe9e709da761c20138d25b6", + "sha256:27858d688a58cbfdd4434e1c40f6c79eb5014b709e725c180488ccdf2f721729", + "sha256:354601a1d1a1322ae5920ba397c58d06c29728a15113598d1a8158647aaa5385", + "sha256:9c3a768486194b4592c7ae9374faa55b37b9877fd9746fb4028cb0ac38fd4c60", + "sha256:c1fd45931889dc1812ba61a517630d126f6185f688eac1693171c6524901b7de", + "sha256:d463a142298112426ebd57351b45c39adb41341b91f033aa903fa4c6f76abecc", + "sha256:e1494d20ffe7891d07d8cb9a8b306c1a38d48b13575265d090fc08910c56d474", + "sha256:ec4b4b638b84d42fc48139f9352f6c6587ee1018d55253542ee28db7480cc653", + "sha256:fa0a570e0a30b9dd618bffbece590ae15726b47f9f1eaf7518dfb35f4d7dcd21" ], - "version": "==5.6.0" + "version": "==5.6.1" }, "pybgpranking": { "editable": true, "git": "https://github.com/D4-project/BGP-Ranking.git/", - "ref": "37c97ae252ec4bf1d67733a49d4895c8cb009cf9", + "ref": "4f2898af7c4e237b6497831d5acf3f4531ac14d8", "subdirectory": "client" }, "pydnstrails": { @@ -335,13 +335,13 @@ "pyipasnhistory": { "editable": true, "git": "https://github.com/D4-project/IPASN-History.git/", - "ref": "e846cd36fe1ed6b22f60890bba89f84e61b62e59", + "ref": "7ef09cf761fc58aa774ea305a33ba75959e39887", "subdirectory": "client" }, "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "b8759673b91e733c307698abdc0d5ed82fd7e0de" + "ref": "1dddfd72e08886673e57e23627064f9ea8303d4c" }, "pyonyphe": { "editable": true, @@ -391,19 +391,19 @@ }, "pyyaml": { "hashes": [ - "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", - "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", - "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", - "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", - "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", - "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", - "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", - "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", - "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", - "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", - "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" + "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", + "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", + "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", + "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", + "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", + "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", + "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", + "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", + "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", + "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", + "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" ], - "version": "==3.13" + "version": "==5.1" }, "rdflib": { "hashes": [ @@ -764,11 +764,11 @@ }, "pytest": { "hashes": [ - "sha256:067a1d4bf827ffdd56ad21bd46674703fce77c5957f6c1eef731f6146bfcef1c", - "sha256:9687049d53695ad45cf5fdc7bbd51f0c49f1ea3ecfc4b7f3fde7501b541f17f4" + "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523", + "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4" ], "index": "pypi", - "version": "==4.3.0" + "version": "==4.3.1" }, "requests": { "hashes": [ From b89d068c041516f020877414bb18b0767fe43d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 2 Apr 2019 15:30:11 +0200 Subject: [PATCH 351/401] new: Modules for greynoise, haveibeenpwned and macvendors Source: https://github.com/src7/misp-modules --- misp_modules/modules/expansion/greynoise.py | 40 ++++++++++++++ misp_modules/modules/expansion/hibp.py | 40 ++++++++++++++ misp_modules/modules/expansion/macvendors.py | 42 +++++++++++++++ tests/test_expansions.py | 57 ++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 misp_modules/modules/expansion/greynoise.py create mode 100644 misp_modules/modules/expansion/hibp.py create mode 100644 misp_modules/modules/expansion/macvendors.py create mode 100644 tests/test_expansions.py diff --git a/misp_modules/modules/expansion/greynoise.py b/misp_modules/modules/expansion/greynoise.py new file mode 100644 index 0000000..324423d --- /dev/null +++ b/misp_modules/modules/expansion/greynoise.py @@ -0,0 +1,40 @@ +import requests +import json + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['ip-dst', 'ip-src'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Aurélien Schwab ', 'description': 'Module to access GreyNoise.io API.', 'module-type': ['hover']} +moduleconfig = ['user-agent']#TODO take this into account in the code + +greynoise_api_url = 'http://api.greynoise.io:8888/v1/query/ip' +default_user_agent = 'MISP-Module' + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + for input_type in mispattributes['input']: + if input_type in request: + ip = request[input_type] + break + else: + misperrors['error'] = "Unsupported attributes type" + return misperrors + data = {'ip':ip} + r = requests.post(greynoise_api_url, data=data, headers={'user-agent': default_user_agent})#Real request + if r.status_code == 200:#OK (record found) + response = json.loads(r.text) + if response: + return {'results': [{'types': mispattributes['output'], 'values': response}]} + elif r.status_code == 404:#Not found (not an error) + return {'results': [{'types': mispattributes['output'], 'values': 'No data'}]} + else:#Real error + misperrors['error'] = 'GreyNoise API not accessible (HTTP ' + str(r.status_code) + ')' + return misperrors['error'] + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinf diff --git a/misp_modules/modules/expansion/hibp.py b/misp_modules/modules/expansion/hibp.py new file mode 100644 index 0000000..7010aee --- /dev/null +++ b/misp_modules/modules/expansion/hibp.py @@ -0,0 +1,40 @@ +import requests +import json + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['email-dst', 'email-src'], 'output': ['text']}#All mails as input +moduleinfo = {'version': '0.1', 'author': 'Aurélien Schwab', 'description': 'Module to access haveibeenpwned.com API.', 'module-type': ['hover']} +moduleconfig = ['user-agent']#TODO take this into account in the code + +haveibeenpwned_api_url = 'https://api.haveibeenpwned.com/api/v2/breachedaccount/' +default_user_agent = 'MISP-Module'#User agent (must be set, requiered by API)) + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + for input_type in mispattributes['input']: + if input_type in request: + email = request[input_type] + break + else: + misperrors['error'] = "Unsupported attributes type" + return misperrors + + r = requests.get(haveibeenpwned_api_url + email, headers={'user-agent': default_user_agent})#Real request + if r.status_code == 200:##OK (record found) + breaches = json.loads(r.text) + if breaches: + return {'results': [{'types': mispattributes['output'], 'values': breaches}]} + elif r.status_code == 404:#Not found (not an error) + return {'results': [{'types': mispattributes['output'], 'values': 'OK (Not Found)'}]} + else:#Real error + misperrors['error'] = 'haveibeenpwned.com API not accessible (HTTP ' + str(r.status_code) + ')' + return misperrors['error'] + +def introspection(): + return mispattributes + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinf diff --git a/misp_modules/modules/expansion/macvendors.py b/misp_modules/modules/expansion/macvendors.py new file mode 100644 index 0000000..55d0ef3 --- /dev/null +++ b/misp_modules/modules/expansion/macvendors.py @@ -0,0 +1,42 @@ +import requests +import json + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['mac-address'], 'output': ['text']} +moduleinfo = {'version': '0.1', 'author': 'Aurélien Schwab', 'description': 'Module to access Macvendors API.', 'module-type': ['hover']} +moduleconfig = ['user-agent'] # TODO take this into account in the code + +macvendors_api_url = 'https://api.macvendors.com/' +default_user_agent = 'MISP-Module' + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + for input_type in mispattributes['input']: + if input_type in request: + mac = request[input_type] + break + else: + misperrors['error'] = "Unsupported attributes type" + return misperrors + r = requests.get(macvendors_api_url + mac, headers={'user-agent': default_user_agent}) # Real request + if r.status_code == 200: # OK (record found) + response = r.text + if response: + return {'results': [{'types': mispattributes['output'], 'values': response}]} + elif r.status_code == 404: # Not found (not an error) + return {'results': [{'types': mispattributes['output'], 'values': 'Not found'}]} + else: # Real error + misperrors['error'] = 'MacVendors API not accessible (HTTP ' + str(r.status_code) + ')' + return misperrors['error'] + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/tests/test_expansions.py b/tests/test_expansions.py new file mode 100644 index 0000000..d581a31 --- /dev/null +++ b/tests/test_expansions.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import unittest +import requests +from urllib.parse import urljoin + + +class TestExpansions(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + self.headers = {'Content-Type': 'application/json'} + self.url = "http://127.0.0.1:6666/" + + def misp_modules_post(self, query): + return requests.post(urljoin(self.url, "query"), json=query) + + def get_values(self, response): + return response.json()['results'][0]['values'] + + def test_cve(self): + query = {"module": "cve", "vulnerability": "CVE-2010-3333"} + response = self.misp_modules_post(query) + self.assertTrue(self.get_values(response).startswith("Stack-based buffer overflow in Microsoft Office XP SP3, Office 2003 SP3")) + + def test_dns(self): + query = {"module": "dns", "hostname": "www.circl.lu", "config": {"nameserver": "8.8.8.8"}} + response = self.misp_modules_post(query) + self.assertEqual(self.get_values(response), ['149.13.33.14']) + + def test_macvendors(self): + query = {"module": "macvendors", "mac-address": "FC-A1-3E-2A-1C-33"} + response = self.misp_modules_post(query) + self.assertEqual(self.get_values(response), 'Samsung Electronics Co.,Ltd') + + def test_haveibeenpwned(self): + query = {"module": "hibp", "email-src": "info@circl.lu"} + response = self.misp_modules_post(query) + self.assertEqual(self.get_values(response), 'OK (Not Found)') + + def test_greynoise(self): + query = {"module": "greynoise", "ip-dst": "1.1.1.1"} + response = self.misp_modules_post(query) + self.assertEqual(self.get_values(response)['status'], 'ok') + + def test_ipasn(self): + query = {"module": "ipasn", "ip-dst": "1.1.1.1"} + response = self.misp_modules_post(query) + key = list(self.get_values(response)['response'].keys())[0] + entry = self.get_values(response)['response'][key]['asn'] + self.assertEqual(entry, '13335') + + def test_bgpranking(self): + query = {"module": "bgpranking", "AS": "13335"} + response = self.misp_modules_post(query) + self.assertEqual(self.get_values(response)['response']['asn_description'], 'CLOUDFLARENET - Cloudflare, Inc., US') From 9ea9816ad3911f0833d337f7b44cd580e940b35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 2 Apr 2019 15:31:50 +0200 Subject: [PATCH 352/401] chg: Bump dependencies --- Pipfile.lock | 180 +++++++++++++++++++++++++-------------------------- 1 file changed, 88 insertions(+), 92 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 60f1d03..b5faca1 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -253,39 +253,35 @@ }, "pillow": { "hashes": [ - "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", - "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", - "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", - "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", - "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", - "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", - "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", - "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", - "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", - "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", - "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", - "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", - "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", - "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", - "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", - "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", - "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", - "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", - "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", - "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", - "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", - "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", - "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", - "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", - "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", - "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", - "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", - "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", - "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", - "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e" + "sha256:15c056bfa284c30a7f265a41ac4cbbc93bdbfc0dfe0613b9cb8a8581b51a9e55", + "sha256:1a4e06ba4f74494ea0c58c24de2bb752818e9d504474ec95b0aa94f6b0a7e479", + "sha256:1c3c707c76be43c9e99cb7e3d5f1bee1c8e5be8b8a2a5eeee665efbf8ddde91a", + "sha256:1fd0b290203e3b0882d9605d807b03c0f47e3440f97824586c173eca0aadd99d", + "sha256:24114e4a6e1870c5a24b1da8f60d0ba77a0b4027907860188ea82bd3508c80eb", + "sha256:258d886a49b6b058cd7abb0ab4b2b85ce78669a857398e83e8b8e28b317b5abb", + "sha256:33c79b6dd6bc7f65079ab9ca5bebffb5f5d1141c689c9c6a7855776d1b09b7e8", + "sha256:367385fc797b2c31564c427430c7a8630db1a00bd040555dfc1d5c52e39fcd72", + "sha256:3c1884ff078fb8bf5f63d7d86921838b82ed4a7d0c027add773c2f38b3168754", + "sha256:44e5240e8f4f8861d748f2a58b3f04daadab5e22bfec896bf5434745f788f33f", + "sha256:46aa988e15f3ea72dddd81afe3839437b755fffddb5e173886f11460be909dce", + "sha256:74d90d499c9c736d52dd6d9b7221af5665b9c04f1767e35f5dd8694324bd4601", + "sha256:809c0a2ce9032cbcd7b5313f71af4bdc5c8c771cb86eb7559afd954cab82ebb5", + "sha256:85d1ef2cdafd5507c4221d201aaf62fc9276f8b0f71bd3933363e62a33abc734", + "sha256:8c3889c7681af77ecfa4431cd42a2885d093ecb811e81fbe5e203abc07e0995b", + "sha256:9218d81b9fca98d2c47d35d688a0cea0c42fd473159dfd5612dcb0483c63e40b", + "sha256:9aa4f3827992288edd37c9df345783a69ef58bd20cc02e64b36e44bcd157bbf1", + "sha256:9d80f44137a70b6f84c750d11019a3419f409c944526a95219bea0ac31f4dd91", + "sha256:b7ebd36128a2fe93991293f997e44be9286503c7530ace6a55b938b20be288d8", + "sha256:c4c78e2c71c257c136cdd43869fd3d5e34fc2162dc22e4a5406b0ebe86958239", + "sha256:c6a842537f887be1fe115d8abb5daa9bc8cc124e455ff995830cc785624a97af", + "sha256:cf0a2e040fdf5a6d95f4c286c6ef1df6b36c218b528c8a9158ec2452a804b9b8", + "sha256:cfd28aad6fc61f7a5d4ee556a997dc6e5555d9381d1390c00ecaf984d57e4232", + "sha256:dca5660e25932771460d4688ccbb515677caaf8595f3f3240ec16c117deff89a", + "sha256:de7aedc85918c2f887886442e50f52c1b93545606317956d65f342bd81cb4fc3", + "sha256:e6c0bbf8e277b74196e3140c35f9a1ae3eafd818f7f2d3a15819c49135d6c062" ], "index": "pypi", - "version": "==5.4.1" + "version": "==6.0.0" }, "psutil": { "hashes": [ @@ -304,7 +300,7 @@ "pybgpranking": { "editable": true, "git": "https://github.com/D4-project/BGP-Ranking.git/", - "ref": "4f2898af7c4e237b6497831d5acf3f4531ac14d8", + "ref": "019ef1c40aad1e5bb5c5072c9a998c6a8f0271f3", "subdirectory": "client" }, "pydnstrails": { @@ -335,13 +331,13 @@ "pyipasnhistory": { "editable": true, "git": "https://github.com/D4-project/IPASN-History.git/", - "ref": "7ef09cf761fc58aa774ea305a33ba75959e39887", + "ref": "0c4f11792061417b77ca6e22d2ece18109d74c75", "subdirectory": "client" }, "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "1dddfd72e08886673e57e23627064f9ea8303d4c" + "ref": "64bcaad0e578129543cdffad532a232722615f6c" }, "pyonyphe": { "editable": true, @@ -414,44 +410,44 @@ }, "redis": { "hashes": [ - "sha256:724932360d48e5407e8f82e405ab3650a36ed02c7e460d1e6fddf0f038422b54", - "sha256:9b19425a38fd074eb5795ff2b0d9a55b46a44f91f5347995f27e3ad257a7d775" + "sha256:6946b5dca72e86103edc8033019cc3814c031232d339d5f4533b02ea85685175", + "sha256:8ca418d2ddca1b1a850afa1680a7d2fd1f3322739271de4b704e0d4668449273" ], - "version": "==3.2.0" + "version": "==3.2.1" }, "reportlab": { "hashes": [ - "sha256:069f684cd0aaa518a27dc9124aed29cee8998e21ddf19604e53214ec8462bdd7", - "sha256:09b68ec01d86b4b120456b3f3202570ec96f57624e3a4fc36f3829323391daa4", - "sha256:0c32be9a406172c29ea20ff55a709ccac1e7fb09f15aba67cb7b455fd1d3dbe0", - "sha256:233196cf25e97cfe7c452524ea29d9a4909f1cb66599299233be1efaaaa7a7a3", - "sha256:2b5e4533f3e5b962835a5ce44467e66d1ecc822761d1b508077b5087a06be338", - "sha256:2e860bcdace5a558356802a92ae8658d7e5fdaa00ded82e83a3f2987c562cb66", - "sha256:3546029e63a9a9dc24ee38959eb417678c2425b96cd27b31e09e216dafc94666", - "sha256:4452b93f9c73b6b70311e7d69082d64da81b38e91bfb4766397630092e6da6fd", - "sha256:528c74a1c6527d1859c2c7a64a94a1cba485b00175162ea23699ae58a1e94939", - "sha256:6116e750f98018febc08dfee6df20446cf954adbcfa378d2c703d56c8864aff3", - "sha256:6b2b3580c647d75ef129172cb3da648cdb24566987b0b59c5ebb80ab770748d6", - "sha256:727b5f2bed08552d143fc99649b1863c773729f580a416844f9d9967bb0a1ae8", - "sha256:74c24a3ec0a3d4f8acb13a07192f45bdb54a1cc3c2286241677e7e8bcd5011fa", - "sha256:98ccd2f8b4f8636db05f3f14db0b471ad6bb4b66ae0dc9052c4822b3bd5d6a7d", - "sha256:a5905aa567946bc938b489a7249c7890c3fd3c9b7b5680dece5bc551c2ddbe0d", - "sha256:acbb7f676b8586b770719e9683eda951fdb38eb7970d46fcbf3cdda88d912a64", - "sha256:b5e30f865add48cf880f1c363eb505b97f2f7baaa88c155f87a335a76515a3e5", - "sha256:be2a7c33a2c28bbd3f453ffe4f0e5200b88c803a097f4cf52d69c6b53fad7a8f", - "sha256:c356bb600f59ac64955813d6497a08bfd5d0c451cb5829b61e3913d0ac084e26", - "sha256:c7ec4ae2393beab584921b1287a04e94fd98c28315e348362d89b85f4b464546", - "sha256:d476edc831bb3e9ebd04d1403abaf3ea57b3e4c2276c91a54fdfb6efbd3f9d97", - "sha256:db059e1a0691c872784062421ec51848539eb4f5210142682e61059a5ca7cc55", - "sha256:dd423a6753509ab14a0ac1b5be39d219c8f8d3781cce3deb4f45eda31969b5e8", - "sha256:ed9b7c0d71ce6fe2b31c6cde530ad8238632b876a5d599218739bda142a77f7c", - "sha256:f0a2465af4006f97b05e1f1546d67d3a3213d414894bf28be7f87f550a7f4a55", - "sha256:f20bfe26e57e8e1f575a9e0325be04dd3562db9f247ffdd73b5d4df6dec53bc2", - "sha256:f3463f2cb40a1b515ac0133ba859eca58f53b56760da9abb27ed684c565f853c", - "sha256:facc3c9748ab1525fb8401a1223bce4f24f0d6aa1a9db86c55db75777ccf40f9" + "sha256:0135bc54a463db5315c93bba4182fb83dc088fefaa7da18784ecd2a0c4a9c068", + "sha256:09e167e01458ea1e0cf3acff634ae9ecc1f1757e7585060d039c90b762859cfd", + "sha256:0dfcea18ba3ca1fac55cb273d056a8a43a48bd04d419299b3267e1994c72455a", + "sha256:1a61e56593ea1a8a38135eedfb40f79dcad13164fff034313ebf2a30e200ca79", + "sha256:1bdd871c2087d3853a0e9a3a573b1a7535500f3341944b1e34e68f3213cd28b8", + "sha256:26878a4b9c45f046c635b5695681188c19806f08b04129ea01c9ed51c7754039", + "sha256:27c62264c758aa30113df105da816223d149e4e87ee778ad49469725b79be2eb", + "sha256:29a9dd3954465b9e4efb129ffda9ab3e6a4f06488e8aa2efd5aff8ad332f13c2", + "sha256:5740e3218ca98c1bc86bd2d2e2a8c1d23e7c97d949d6377ac30aaf449f01c363", + "sha256:605892bb3f822a1e7342ce2b461d645ab8e4d13875127c0ae5377f76853db422", + "sha256:6dacc72552bc0dd50286e856f09a5e646a007d9345598bf6f75b117a200bfd9d", + "sha256:7021b7c8ba6d8e69e4c68c9473067482aaa40b9094270b45dbf798fcb0e09bd4", + "sha256:8acd950dad5b20a417579d1253c1065222dde48f9412e71533b052ab3dd98632", + "sha256:8b8fb3b0dd1e2124aba24544a02c95bff1fffa966b0581f30abf4fb28e414005", + "sha256:920c61c942eb1cc446e1647a04978f4afe31993ed403b74576a018c3ca526394", + "sha256:928e8d99befe064e28e9a29a4fd9afcf2066dcd758b0903280e67e221527422a", + "sha256:a04787eee401a74c80b65e539b5fe9226fdeabe25caa3d216c21dc990b2f8a01", + "sha256:a5bb6bd7753cba854425fcf7ecf04627a17de78d47ef9e8fac615887c5658da3", + "sha256:a70d970619014dc83b4406bcfed7e2f9d5aaf5f521aad808f5560d90ea896fb4", + "sha256:ae468fe82c8af3d1987113f03c1f87d01daa5b4c85c1f10da126be84423a744d", + "sha256:b278d83a7f76410bd310b368309e6e4b19664ffa686abfa9f0696130b09c17d3", + "sha256:b6623e9a96db3edc4b384e036e67c7bc87bbd7e5dc2d72ce66efa0043f9383b0", + "sha256:dc15cfa577bb25f0a598d483cf6dcc5ecad576ba723fe9bec63b6ec720dab2a3", + "sha256:dffdb4f6b34ce791e67365f3f96ab3c45b4cdd2c70d212fac98fb146dc75ac80", + "sha256:e84020e3482856da733e1359cb7b84e6bac09179bd3af860e70468a9c3cb43e3", + "sha256:edda09668e8474d5acb1a37fb64599557b43a714f1469bd49a058e95b5b410ff", + "sha256:f77e9835873931d25f836a3c107e53e0f7d3c0b4906b13063815308cf5ca1fac", + "sha256:f91d16ff07d5d3c92303f64c6864d74d3b6a491dde186bfef90c58088f932998" ], "index": "pypi", - "version": "==3.5.13" + "version": "==3.5.17" }, "requests": { "hashes": [ @@ -477,10 +473,10 @@ }, "sigmatools": { "hashes": [ - "sha256:3bdbd2ee99c32f245e948d6b882219729ab379685dd7366e4d6149c390e08170" + "sha256:ae980b6d6fd466294911efa493934d24e3c5df406da4a190b9fff0943a81cc5f" ], "index": "pypi", - "version": "==0.9" + "version": "==0.10" }, "six": { "hashes": [ @@ -491,10 +487,10 @@ }, "soupsieve": { "hashes": [ - "sha256:afa56bf14907bb09403e5d15fbed6275caa4174d36b975226e3b67a3bb6e2c4b", - "sha256:eaed742b48b1f3e2d45ba6f79401b2ed5dc33b2123dfe216adb90d4bfa0ade26" + "sha256:3aef141566afd07201b525c17bfaadd07580a8066f82b57f7c9417f26adbd0a3", + "sha256:e41a65e99bd125972d84221022beb1e4b5cfc68fa12c170c39834ce32d1b294c" ], - "version": "==1.8" + "version": "==1.9" }, "sparqlwrapper": { "hashes": [ @@ -520,15 +516,15 @@ }, "tornado": { "hashes": [ - "sha256:1a58f2d603476d5e462f7c28ca1dbb5ac7e51348b27a9cac849cdec3471101f8", - "sha256:33f93243cd46dd398e5d2bbdd75539564d1f13f25d704cfc7541db74066d6695", - "sha256:34e59401afcecf0381a28228daad8ed3275bcb726810654612d5e9c001f421b7", - "sha256:35817031611d2c296c69e5023ea1f9b5720be803e3bb119464bb2a0405d5cd70", - "sha256:666b335cef5cc2759c21b7394cff881f71559aaf7cb8c4458af5bb6cb7275b47", - "sha256:81203efb26debaaef7158187af45bc440796de9fb1df12a75b65fae11600a255", - "sha256:de274c65f45f6656c375cdf1759dbf0bc52902a1e999d12a35eb13020a641a53" + "sha256:1174dcb84d08887b55defb2cda1986faeeea715fff189ef3dc44cce99f5fca6b", + "sha256:2613fab506bd2aedb3722c8c64c17f8f74f4070afed6eea17f20b2115e445aec", + "sha256:44b82bc1146a24e5b9853d04c142576b4e8fa7a92f2e30bc364a85d1f75c4de2", + "sha256:457fcbee4df737d2defc181b9073758d73f54a6cfc1f280533ff48831b39f4a8", + "sha256:49603e1a6e24104961497ad0c07c799aec1caac7400a6762b687e74c8206677d", + "sha256:8c2f40b99a8153893793559919a355d7b74649a11e59f411b0b0a1793e160bc0", + "sha256:e1d897889c3b5a829426b7d52828fb37b28bc181cd598624e65c8be40ee3f7fa" ], - "version": "==6.0.1" + "version": "==6.0.2" }, "url-normalize": { "hashes": [ @@ -559,20 +555,20 @@ }, "vulners": { "hashes": [ - "sha256:08a7ccb2b210d45143354c6161c73fe209dc14fae8692e8b793b36b79330ad11", - "sha256:bfe2478cc11c69ba7e436d7a5df925e227565782c0bd603929fb3d612c73d78d", - "sha256:d035f6a883625878a1dc377830d17d9702ef138ca31569ac01cb8686874f89cd" + "sha256:6617d5904b5369507bc34105071d312e9e1c38d73654505e7b15b9a3f1325915", + "sha256:8b05d12a9dd7cbc07198a13281299a6e014ec348522e214b1efd097e194b7568", + "sha256:a19b02e0a112d70951e10c5abc1993f7f029234212828e1b617ab35f4e460a24" ], "index": "pypi", - "version": "==1.4.5" + "version": "==1.4.7" }, "wand": { "hashes": [ - "sha256:7d6b8dc9d4eaccc430b9c86e6b749013220c994970a3f39e902b397e2fa732c3", - "sha256:cc0b5c9cd50fecd10dc8888b739dd5984c6f8085d2954f34903b83ca39a91236" + "sha256:91810d241ab0851d40e67c946beb960b869c4f4160c397eac291ec6283ee3e3f", + "sha256:ae7c0958509a22f531b7b97e93adfd3f1208f0ac1c593af9e5f0cffa4ac06d5b" ], "index": "pypi", - "version": "==0.5.1" + "version": "==0.5.2" }, "xlsxwriter": { "hashes": [ @@ -719,11 +715,11 @@ }, "more-itertools": { "hashes": [ - "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40", - "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1" + "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", + "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" ], "markers": "python_version > '2.7'", - "version": "==6.0.0" + "version": "==7.0.0" }, "nose": { "hashes": [ @@ -764,11 +760,11 @@ }, "pytest": { "hashes": [ - "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523", - "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4" + "sha256:13c5e9fb5ec5179995e9357111ab089af350d788cbc944c628f3cde72285809b", + "sha256:f21d2f1fb8200830dcbb5d8ec466a9c9120e20d8b53c7585d180125cce1d297a" ], "index": "pypi", - "version": "==4.3.1" + "version": "==4.4.0" }, "requests": { "hashes": [ From c64f514a6fdfb21397dcddd8feddf7ee90bf5767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 2 Apr 2019 15:39:27 +0200 Subject: [PATCH 353/401] fix: Typos in variable names --- misp_modules/modules/expansion/greynoise.py | 2 +- misp_modules/modules/expansion/hibp.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/greynoise.py b/misp_modules/modules/expansion/greynoise.py index 324423d..66f6985 100644 --- a/misp_modules/modules/expansion/greynoise.py +++ b/misp_modules/modules/expansion/greynoise.py @@ -37,4 +37,4 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig - return moduleinf + return moduleinfo diff --git a/misp_modules/modules/expansion/hibp.py b/misp_modules/modules/expansion/hibp.py index 7010aee..67af644 100644 --- a/misp_modules/modules/expansion/hibp.py +++ b/misp_modules/modules/expansion/hibp.py @@ -37,4 +37,4 @@ def introspection(): def version(): moduleinfo['config'] = moduleconfig - return moduleinf + return moduleinfo From 9cb21f98e1b6670d733940ea74d75a7a01a1b38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 2 Apr 2019 15:46:17 +0200 Subject: [PATCH 354/401] fix: Add the new module sin the list of modules availables. --- 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 d8fb153..42f74a3 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -8,4 +8,4 @@ __all__ = ['vmray_submit', 'bgpranking', 'circl_passivedns', 'circl_passivessl', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', - 'intel471', 'backscatter_io', 'btc_scam_check'] + 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors'] From f82933779f1567e1d4325f86a29b4459c9d624cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 2 Apr 2019 16:01:33 +0200 Subject: [PATCH 355/401] fix: pep8 foobar. --- misp_modules/modules/expansion/greynoise.py | 15 +++++++++------ misp_modules/modules/expansion/hibp.py | 17 ++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/misp_modules/modules/expansion/greynoise.py b/misp_modules/modules/expansion/greynoise.py index 66f6985..d26736d 100644 --- a/misp_modules/modules/expansion/greynoise.py +++ b/misp_modules/modules/expansion/greynoise.py @@ -4,11 +4,12 @@ import json misperrors = {'error': 'Error'} mispattributes = {'input': ['ip-dst', 'ip-src'], 'output': ['text']} moduleinfo = {'version': '0.1', 'author': 'Aurélien Schwab ', 'description': 'Module to access GreyNoise.io API.', 'module-type': ['hover']} -moduleconfig = ['user-agent']#TODO take this into account in the code +moduleconfig = ['user-agent'] # TODO take this into account in the code greynoise_api_url = 'http://api.greynoise.io:8888/v1/query/ip' default_user_agent = 'MISP-Module' + def handler(q=False): if q is False: return False @@ -20,21 +21,23 @@ def handler(q=False): else: misperrors['error'] = "Unsupported attributes type" return misperrors - data = {'ip':ip} - r = requests.post(greynoise_api_url, data=data, headers={'user-agent': default_user_agent})#Real request - if r.status_code == 200:#OK (record found) + data = {'ip': ip} + r = requests.post(greynoise_api_url, data=data, headers={'user-agent': default_user_agent}) # Real request + if r.status_code == 200: # OK (record found) response = json.loads(r.text) if response: return {'results': [{'types': mispattributes['output'], 'values': response}]} - elif r.status_code == 404:#Not found (not an error) + elif r.status_code == 404: # Not found (not an error) return {'results': [{'types': mispattributes['output'], 'values': 'No data'}]} - else:#Real error + else: # Real error misperrors['error'] = 'GreyNoise API not accessible (HTTP ' + str(r.status_code) + ')' return misperrors['error'] + def introspection(): return mispattributes + def version(): moduleinfo['config'] = moduleconfig return moduleinfo diff --git a/misp_modules/modules/expansion/hibp.py b/misp_modules/modules/expansion/hibp.py index 67af644..8db3fa7 100644 --- a/misp_modules/modules/expansion/hibp.py +++ b/misp_modules/modules/expansion/hibp.py @@ -2,12 +2,13 @@ import requests import json misperrors = {'error': 'Error'} -mispattributes = {'input': ['email-dst', 'email-src'], 'output': ['text']}#All mails as input +mispattributes = {'input': ['email-dst', 'email-src'], 'output': ['text']} # All mails as input moduleinfo = {'version': '0.1', 'author': 'Aurélien Schwab', 'description': 'Module to access haveibeenpwned.com API.', 'module-type': ['hover']} -moduleconfig = ['user-agent']#TODO take this into account in the code +moduleconfig = ['user-agent'] # TODO take this into account in the code haveibeenpwned_api_url = 'https://api.haveibeenpwned.com/api/v2/breachedaccount/' -default_user_agent = 'MISP-Module'#User agent (must be set, requiered by API)) +default_user_agent = 'MISP-Module' # User agent (must be set, requiered by API)) + def handler(q=False): if q is False: @@ -21,20 +22,22 @@ def handler(q=False): misperrors['error'] = "Unsupported attributes type" return misperrors - r = requests.get(haveibeenpwned_api_url + email, headers={'user-agent': default_user_agent})#Real request - if r.status_code == 200:##OK (record found) + r = requests.get(haveibeenpwned_api_url + email, headers={'user-agent': default_user_agent}) # Real request + if r.status_code == 200: # OK (record found) breaches = json.loads(r.text) if breaches: return {'results': [{'types': mispattributes['output'], 'values': breaches}]} - elif r.status_code == 404:#Not found (not an error) + elif r.status_code == 404: # Not found (not an error) return {'results': [{'types': mispattributes['output'], 'values': 'OK (Not Found)'}]} - else:#Real error + else: # Real error misperrors['error'] = 'haveibeenpwned.com API not accessible (HTTP ' + str(r.status_code) + ')' return misperrors['error'] + def introspection(): return mispattributes + def version(): moduleinfo['config'] = moduleconfig return moduleinfo From 07a66d62b065bfe06225f5a0d1f6d95247e6d9b2 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 2 Apr 2019 20:03:11 +0200 Subject: [PATCH 356/401] chg: [doc] new modules added --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e890433..5669b19 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,14 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [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). * [Farsight DNSDB Passive DNS](misp_modules/modules/expansion/farsight_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. * [GeoIP](misp_modules/modules/expansion/geoip_country.py) - a hover and expansion module to get GeoIP information from geolite/maxmind. +* [Greynoise](misp_modules/modules/expansion/greynoise.py) - a hover to get information from greynoise. * [hashdd](misp_modules/modules/expansion/hashdd.py) - a hover module to check file hashes against [hashdd.com](http://www.hashdd.com) including NSLR dataset. +* [hibp](misp_modules/modules/expansion/hibp.py) - a hover module to lookup against Have I Been Pwned? * [intel471](misp_modules/modules/expansion/intel471.py) - an expansion module to get info from [Intel471](https://intel471.com). * [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 from [MAC address Vendor Lookup](https://macaddress.io). See [integration tutorial here](https://macaddress.io/integrations/MISP-module). +* [macvendors](misp_modules/modules/expansion/macvendors.py) - a hover module to retrieve mac vendor information. * [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 5ed91dcec2bf4509a5b3142f29c9ae417e6e1c02 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Mon, 8 Apr 2019 16:03:41 +0900 Subject: [PATCH 357/401] fix: [doc] Small typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5669b19..18b5548 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ scl enable rh-python36 ‘python3 –m pip install cryptography’ scl enable rh-python36 ‘python3 –m pip install -I -r REQUIREMENTS’ scl enable rh-python36 ‘python3 –m pip install –I .’ ~~~~ -Create the service file /etc/systemd/system/misp-workers.service : +Create the service file /etc/systemd/system/misp-modules.service : ~~~~ [Unit] Description=MISP's modules From b5f2424f274813e044ba88210628575855263b5e Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Mon, 8 Apr 2019 16:17:22 +0900 Subject: [PATCH 358/401] chg: [doc] Updated README to reflect current virtualenv efforts. TODO: pipenv --- README.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 18b5548..400356e 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [ThreatAnalyzer](misp_modules/modules/import_mod/threatanalyzer_import.py) - An import module to process ThreatAnalyzer archive.zip/analysis.json sandbox exports. * [VMRay](misp_modules/modules/import_mod/vmray_import.py) - An import module to process VMRay export. -## How to install and start MISP modules in a Python virtualenv? +## How to install and start MISP modules in a Python virtualenv? (recommended) ~~~~bash sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick virtualenv @@ -97,7 +97,10 @@ sudo git clone https://github.com/MISP/misp-modules.git cd misp-modules sudo -u www-data /var/www/MISP/venv/bin/pip install -I -r REQUIREMENTS sudo -u www-data /var/www/MISP/venv/bin/pip install . -sudo sed -i -e '$i \sudo -u www-data /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s > /tmp/misp-modules_rc.local.log &\n' /etc/rc.local +# Start misp-modules as a service +sudo cp etc/systemd/system/misp-modules.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now misp-modules /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules ~~~~ @@ -110,7 +113,10 @@ sudo git clone https://github.com/MISP/misp-modules.git cd misp-modules sudo pip3 install -I -r REQUIREMENTS sudo pip3 install -I . -sudo sed -i -e '$i \sudo -u www-data /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s > /tmp/misp-modules_rc.local.log &\n' /etc/rc.local +# Start misp-modules as a service +sudo cp etc/systemd/system/misp-modules.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now misp-modules /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules ~~~~ @@ -490,7 +496,7 @@ Download a pre-built virtual image from the [MISP training materials](https://ww - Create a Host-Only adapter in VirtualBox - Set your Misp OVA to that Host-Only adapter - Start the virtual machine -- Get the IP address of the virutal machine +- Get the IP address of the virtual machine - SSH into the machine (Login info on training page) - Go into the misp-modules directory @@ -510,14 +516,16 @@ Remove the contents of the build directory and re-install misp-modules. ~~~bash sudo rm -fr build/* -sudo pip3 install --upgrade . +sudo -u www-data /var/www/MISP/venv/bin/pip install --upgrade . ~~~ SSH in with a different terminal and run `misp-modules` with debugging enabled. ~~~bash -sudo killall misp-modules -misp-modules -d +# In case misp-modules is not a service do: +# sudo killall misp-modules +sudo systemctl disable --now misp-modules +sudo -u www-data /var/www/MISP/venv/bin/misp-modules -d ~~~ From d24a6e2e249f2d36a92634197c4e4c21a1b3b9b6 Mon Sep 17 00:00:00 2001 From: iceone23 Date: Mon, 15 Apr 2019 06:17:27 -0700 Subject: [PATCH 359/401] Create cisco_firesight_manager_ACL_rule_export.py Cisco Firesight Manager ACL Rule Export module --- ...cisco_firesight_manager_ACL_rule_export.py | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py diff --git a/misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py b/misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py new file mode 100644 index 0000000..dcb6178 --- /dev/null +++ b/misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py @@ -0,0 +1,135 @@ +###################################################### +# # +# Author: Stanislav Klevtsov, Ukraine; Feb 2019. # +# # +# # +# Script was tested on the following configuration: # +# MISP v2.4.90 # +# Cisco Firesight Manager Console v6.2.3 (bld 84) # +# # +###################################################### + +import json +import base64 +import csv +from urllib.parse import quote + +misperrors = {'error': 'Error'} + +moduleinfo = {'version': '1', 'author': 'Stanislav Klevtsov', + 'description': 'Export malicious network activity attributes of the MISP event to Cisco firesight manager block rules', + 'module-type': ['export']} + + +moduleconfig = ['fmc_ip_addr', 'fmc_login', 'fmc_pass', 'domain_id', 'acpolicy_id'] + +fsmapping = {"ip-dst":"dst", "url":"request"} + +mispattributes = {'input':list(fsmapping.keys())} + +# options: event, attribute, event-collection, attribute-collection +inputSource = ['event'] + +outputFileExtension = 'sh' +responseType = 'application/txt' + +# .sh file templates +SH_FILE_HEADER = """#!/bin/sh\n\n""" + +BLOCK_JSON_TMPL = """ +BLOCK_RULE='{{ "action": "BLOCK", "enabled": true, "type": "AccessRule", "name": "{rule_name}", "destinationNetworks": {{ "literals": [ {dst_networks} ] }}, "urls": {{ "literals": [ {urls} ] }}, "newComments": [ "{event_info_comment}" ] }}'\n +""" + +BLOCK_DST_JSON_TMPL = """{{ "type": "Host", "value": "{ipdst}" }} """ +BLOCK_URL_JSON_TMPL = """{{ "type": "Url", "url": "{url}" }} """ + +CURL_ADD_RULE_TMPL = """ +curl -X POST -v -k -H 'Content-Type: application/json' -H \"Authorization: Basic $LOGINPASS_BASE64\" -H \"X-auth-access-token: $ACC_TOKEN\" -i \"https://$FIRESIGHT_IP_ADDR/api/fmc_config/v1/domain/$DOMAIN_ID/policy/accesspolicies/$ACPOLICY_ID/accessrules\" --data \"$BLOCK_RULE\" """ + + +def handler(q=False): + if q is False: + return False + + r = {'results': []} + request = json.loads(q) + + if "config" in request: + config = request["config"] + + #check if config is empty + if not config['fmc_ip_addr']: config['fmc_ip_addr'] = "0.0.0.0" + if not config['fmc_login']: config['fmc_login'] = "login" + if not config['fmc_pass']: config['fmc_pass'] = "password" + if not config['domain_id']: config['domain_id'] = "SET_FIRESIGHT_DOMAIN_ID" + if not config['acpolicy_id']: config['acpolicy_id'] = "SET_FIRESIGHT_ACPOLICY_ID" + + data = request["data"] + output = "" + ipdst = [] + urls = [] + + #populate the ACL rule with attributes + for ev in data: + + event = ev["Attribute"] + event_id = ev["Event"]["id"] + event_info = ev["Event"]["info"] + + for index, attr in enumerate(event): + if attr["to_ids"] is True: + + if attr["type"] in fsmapping: + if attr["type"] in "ip-dst": + ipdst.append(BLOCK_DST_JSON_TMPL.format(ipdst=attr["value"])) + else: + urls.append(BLOCK_URL_JSON_TMPL.format(url=quote(attr["value"], safe='@/:;?&=-_.,+!*'))) + + + #building the .sh file + output += SH_FILE_HEADER + output += "FIRESIGHT_IP_ADDR='" + config['fmc_ip_addr'] + "'\n" + + output += "LOGINPASS_BASE64=`echo -n '" + config['fmc_login'] + ":" + config['fmc_pass'] + "' | base64`\n" + output += "DOMAIN_ID='" + config['domain_id'] + "'\n" + output += "ACPOLICY_ID='" + config['acpolicy_id'] + "'\n\n" + + output += "ACC_TOKEN=`curl -X POST -v -k -sD - -o /dev/null -H \"Authorization: Basic $LOGINPASS_BASE64\" -i \"https://$FIRESIGHT_IP_ADDR/api/fmc_platform/v1/auth/generatetoken\" | grep -i x-auth-acc | sed 's/.*:\\ //g' | tr -d '[:space:]' | tr -d '\\n'`" + "\n" + + output += BLOCK_JSON_TMPL.format(rule_name="misp_event_"+event_id,dst_networks=', '.join(ipdst), urls=', '.join(urls), event_info_comment=event_info) + "\n" + + output += CURL_ADD_RULE_TMPL + # END building the .sh file + + r = {"data":base64.b64encode(output.encode('utf-8')).decode('utf-8')} + return r + + +def introspection(): + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + outputFileExtension + modulesetup['outputFileExtension'] = outputFileExtension + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From f5167c2f230c7dea0725bdb585c6ce8d61496942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 16 Apr 2019 11:25:39 +0200 Subject: [PATCH 360/401] fix: Make flake8 happy. --- ...cisco_firesight_manager_ACL_rule_export.py | 147 +++++++++--------- 1 file changed, 76 insertions(+), 71 deletions(-) diff --git a/misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py b/misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py index dcb6178..ab79692 100644 --- a/misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py +++ b/misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py @@ -11,21 +11,20 @@ import json import base64 -import csv from urllib.parse import quote misperrors = {'error': 'Error'} moduleinfo = {'version': '1', 'author': 'Stanislav Klevtsov', - 'description': 'Export malicious network activity attributes of the MISP event to Cisco firesight manager block rules', - 'module-type': ['export']} + 'description': 'Export malicious network activity attributes of the MISP event to Cisco firesight manager block rules', + 'module-type': ['export']} moduleconfig = ['fmc_ip_addr', 'fmc_login', 'fmc_pass', 'domain_id', 'acpolicy_id'] -fsmapping = {"ip-dst":"dst", "url":"request"} +fsmapping = {"ip-dst": "dst", "url": "request"} -mispattributes = {'input':list(fsmapping.keys())} +mispattributes = {'input': list(fsmapping.keys())} # options: event, attribute, event-collection, attribute-collection inputSource = ['event'] @@ -48,88 +47,94 @@ curl -X POST -v -k -H 'Content-Type: application/json' -H \"Authorization: Basic def handler(q=False): - if q is False: - return False - - r = {'results': []} - request = json.loads(q) + if q is False: + return False - if "config" in request: - config = request["config"] + r = {'results': []} + request = json.loads(q) - #check if config is empty - if not config['fmc_ip_addr']: config['fmc_ip_addr'] = "0.0.0.0" - if not config['fmc_login']: config['fmc_login'] = "login" - if not config['fmc_pass']: config['fmc_pass'] = "password" - if not config['domain_id']: config['domain_id'] = "SET_FIRESIGHT_DOMAIN_ID" - if not config['acpolicy_id']: config['acpolicy_id'] = "SET_FIRESIGHT_ACPOLICY_ID" + if "config" in request: + config = request["config"] - data = request["data"] - output = "" - ipdst = [] - urls = [] + # check if config is empty + if not config['fmc_ip_addr']: + config['fmc_ip_addr'] = "0.0.0.0" + if not config['fmc_login']: + config['fmc_login'] = "login" + if not config['fmc_pass']: + config['fmc_pass'] = "password" + if not config['domain_id']: + config['domain_id'] = "SET_FIRESIGHT_DOMAIN_ID" + if not config['acpolicy_id']: + config['acpolicy_id'] = "SET_FIRESIGHT_ACPOLICY_ID" - #populate the ACL rule with attributes - for ev in data: + data = request["data"] + output = "" + ipdst = [] + urls = [] - event = ev["Attribute"] - event_id = ev["Event"]["id"] - event_info = ev["Event"]["info"] + # populate the ACL rule with attributes + for ev in data: - for index, attr in enumerate(event): - if attr["to_ids"] is True: + event = ev["Attribute"] + event_id = ev["Event"]["id"] + event_info = ev["Event"]["info"] - if attr["type"] in fsmapping: - if attr["type"] in "ip-dst": - ipdst.append(BLOCK_DST_JSON_TMPL.format(ipdst=attr["value"])) - else: - urls.append(BLOCK_URL_JSON_TMPL.format(url=quote(attr["value"], safe='@/:;?&=-_.,+!*'))) + for index, attr in enumerate(event): + if attr["to_ids"] is True: + if attr["type"] in fsmapping: + if attr["type"] == "ip-dst": + ipdst.append(BLOCK_DST_JSON_TMPL.format(ipdst=attr["value"])) + else: + urls.append(BLOCK_URL_JSON_TMPL.format(url=quote(attr["value"], safe='@/:;?&=-_.,+!*'))) + # building the .sh file + output += SH_FILE_HEADER + output += "FIRESIGHT_IP_ADDR='{}'\n".format(config['fmc_ip_addr']) - #building the .sh file - output += SH_FILE_HEADER - output += "FIRESIGHT_IP_ADDR='" + config['fmc_ip_addr'] + "'\n" + output += "LOGINPASS_BASE64=`echo -n '{}:{}' | base64`\n".format(config['fmc_login'], config['fmc_pass']) + output += "DOMAIN_ID='{}'\n".format(config['domain_id']) + output += "ACPOLICY_ID='{}'\n\n".format(config['acpolicy_id']) - output += "LOGINPASS_BASE64=`echo -n '" + config['fmc_login'] + ":" + config['fmc_pass'] + "' | base64`\n" - output += "DOMAIN_ID='" + config['domain_id'] + "'\n" - output += "ACPOLICY_ID='" + config['acpolicy_id'] + "'\n\n" - - output += "ACC_TOKEN=`curl -X POST -v -k -sD - -o /dev/null -H \"Authorization: Basic $LOGINPASS_BASE64\" -i \"https://$FIRESIGHT_IP_ADDR/api/fmc_platform/v1/auth/generatetoken\" | grep -i x-auth-acc | sed 's/.*:\\ //g' | tr -d '[:space:]' | tr -d '\\n'`" + "\n" + output += "ACC_TOKEN=`curl -X POST -v -k -sD - -o /dev/null -H \"Authorization: Basic $LOGINPASS_BASE64\" -i \"https://$FIRESIGHT_IP_ADDR/api/fmc_platform/v1/auth/generatetoken\" | grep -i x-auth-acc | sed 's/.*:\\ //g' | tr -d '[:space:]' | tr -d '\\n'`\n" - output += BLOCK_JSON_TMPL.format(rule_name="misp_event_"+event_id,dst_networks=', '.join(ipdst), urls=', '.join(urls), event_info_comment=event_info) + "\n" + output += BLOCK_JSON_TMPL.format(rule_name="misp_event_{}".format(event_id), + dst_networks=', '.join(ipdst), + urls=', '.join(urls), + event_info_comment=event_info) + "\n" - output += CURL_ADD_RULE_TMPL - # END building the .sh file + output += CURL_ADD_RULE_TMPL + # END building the .sh file - r = {"data":base64.b64encode(output.encode('utf-8')).decode('utf-8')} - return r + r = {"data": base64.b64encode(output.encode('utf-8')).decode('utf-8')} + return r def introspection(): - modulesetup = {} - try: - responseType - modulesetup['responseType'] = responseType - except NameError: - pass - try: - userConfig - modulesetup['userConfig'] = userConfig - except NameError: - pass - try: - outputFileExtension - modulesetup['outputFileExtension'] = outputFileExtension - except NameError: - pass - try: - inputSource - modulesetup['inputSource'] = inputSource - except NameError: - pass - return modulesetup + modulesetup = {} + try: + responseType + modulesetup['responseType'] = responseType + except NameError: + pass + try: + userConfig + modulesetup['userConfig'] = userConfig + except NameError: + pass + try: + outputFileExtension + modulesetup['outputFileExtension'] = outputFileExtension + except NameError: + pass + try: + inputSource + modulesetup['inputSource'] = inputSource + except NameError: + pass + return modulesetup def version(): - moduleinfo['config'] = moduleconfig - return moduleinfo + moduleinfo['config'] = moduleconfig + return moduleinfo From 639534f152f9e94f529bc0c8a55847ddcd1d00f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 16 Apr 2019 11:25:53 +0200 Subject: [PATCH 361/401] chg: Bump Dependencies. --- Pipfile.lock | 106 +++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index b5faca1..428ae11 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -111,10 +111,10 @@ }, "click-plugins": { "hashes": [ - "sha256:b1ee1ccc9421c73007fe290680d97984eb6eaf5f4512b7620c6aa46031d6cb6b", - "sha256:dfed74b5063546a137de99baaaf742b4de4337ad2b3e1df5ec7c8a256adc0847" + "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", + "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8" ], - "version": "==1.0.4" + "version": "==1.1.1" }, "colorama": { "hashes": [ @@ -300,7 +300,7 @@ "pybgpranking": { "editable": true, "git": "https://github.com/D4-project/BGP-Ranking.git/", - "ref": "019ef1c40aad1e5bb5c5072c9a998c6a8f0271f3", + "ref": "86180c080ba5f83a5160a8295ebc263709aa4eba", "subdirectory": "client" }, "pydnstrails": { @@ -331,13 +331,13 @@ "pyipasnhistory": { "editable": true, "git": "https://github.com/D4-project/IPASN-History.git/", - "ref": "0c4f11792061417b77ca6e22d2ece18109d74c75", + "ref": "1009124c39e9cc3b362f0f9ce4d272817b9a5186", "subdirectory": "client" }, "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "64bcaad0e578129543cdffad532a232722615f6c" + "ref": "13445b51748524195bb15aec2c1c6176bcb6bd95" }, "pyonyphe": { "editable": true, @@ -346,10 +346,10 @@ }, "pyparsing": { "hashes": [ - "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a", - "sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3" + "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", + "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" ], - "version": "==2.3.1" + "version": "==2.4.0" }, "pypdns": { "hashes": [ @@ -417,37 +417,37 @@ }, "reportlab": { "hashes": [ - "sha256:0135bc54a463db5315c93bba4182fb83dc088fefaa7da18784ecd2a0c4a9c068", - "sha256:09e167e01458ea1e0cf3acff634ae9ecc1f1757e7585060d039c90b762859cfd", - "sha256:0dfcea18ba3ca1fac55cb273d056a8a43a48bd04d419299b3267e1994c72455a", - "sha256:1a61e56593ea1a8a38135eedfb40f79dcad13164fff034313ebf2a30e200ca79", - "sha256:1bdd871c2087d3853a0e9a3a573b1a7535500f3341944b1e34e68f3213cd28b8", - "sha256:26878a4b9c45f046c635b5695681188c19806f08b04129ea01c9ed51c7754039", - "sha256:27c62264c758aa30113df105da816223d149e4e87ee778ad49469725b79be2eb", - "sha256:29a9dd3954465b9e4efb129ffda9ab3e6a4f06488e8aa2efd5aff8ad332f13c2", - "sha256:5740e3218ca98c1bc86bd2d2e2a8c1d23e7c97d949d6377ac30aaf449f01c363", - "sha256:605892bb3f822a1e7342ce2b461d645ab8e4d13875127c0ae5377f76853db422", - "sha256:6dacc72552bc0dd50286e856f09a5e646a007d9345598bf6f75b117a200bfd9d", - "sha256:7021b7c8ba6d8e69e4c68c9473067482aaa40b9094270b45dbf798fcb0e09bd4", - "sha256:8acd950dad5b20a417579d1253c1065222dde48f9412e71533b052ab3dd98632", - "sha256:8b8fb3b0dd1e2124aba24544a02c95bff1fffa966b0581f30abf4fb28e414005", - "sha256:920c61c942eb1cc446e1647a04978f4afe31993ed403b74576a018c3ca526394", - "sha256:928e8d99befe064e28e9a29a4fd9afcf2066dcd758b0903280e67e221527422a", - "sha256:a04787eee401a74c80b65e539b5fe9226fdeabe25caa3d216c21dc990b2f8a01", - "sha256:a5bb6bd7753cba854425fcf7ecf04627a17de78d47ef9e8fac615887c5658da3", - "sha256:a70d970619014dc83b4406bcfed7e2f9d5aaf5f521aad808f5560d90ea896fb4", - "sha256:ae468fe82c8af3d1987113f03c1f87d01daa5b4c85c1f10da126be84423a744d", - "sha256:b278d83a7f76410bd310b368309e6e4b19664ffa686abfa9f0696130b09c17d3", - "sha256:b6623e9a96db3edc4b384e036e67c7bc87bbd7e5dc2d72ce66efa0043f9383b0", - "sha256:dc15cfa577bb25f0a598d483cf6dcc5ecad576ba723fe9bec63b6ec720dab2a3", - "sha256:dffdb4f6b34ce791e67365f3f96ab3c45b4cdd2c70d212fac98fb146dc75ac80", - "sha256:e84020e3482856da733e1359cb7b84e6bac09179bd3af860e70468a9c3cb43e3", - "sha256:edda09668e8474d5acb1a37fb64599557b43a714f1469bd49a058e95b5b410ff", - "sha256:f77e9835873931d25f836a3c107e53e0f7d3c0b4906b13063815308cf5ca1fac", - "sha256:f91d16ff07d5d3c92303f64c6864d74d3b6a491dde186bfef90c58088f932998" + "sha256:1c228a3ac2c405f7fc16eac43ba92aec448bc25438902f30590ad021e8828097", + "sha256:2210fafd3bb06308a84876fe6d19172b645373edce2b6d7501378cb9c768f825", + "sha256:232fb2037b7c3df259685f1c5ecb7826f55742dc81f0713837b84a152307483e", + "sha256:2c4f25e63fa75f3064871cf435696a4e19b7bd4901d922b766ae58a447b5b6da", + "sha256:47951166d897b60e9e7ca349db82a2b689e6478ac6078e2c7c88ca8becbb0c7d", + "sha256:526ab1193ea8e97c4838135917890e66de5f777d04283008007229b139f3c094", + "sha256:5a9cc8470623ec5b76c7e59f56b7d1fcf0254896cd61842dbdbd278934cc50f4", + "sha256:5ddc1a4a74f225e35a7f60e2eae10de6878dddc9960dad2d9cadc49092f8850d", + "sha256:6b594f6d7d71bc5778e19adb1c699a598c69b9a7bcf97fa638d8762279f9d80a", + "sha256:6e8c89b46cfaf9ae40b7db87e9f29c9e5d32d18d25f9cd10d423a5241e8ec453", + "sha256:71f4f3e3975b91ddbfc1b36a537b46d07533ca7f31945e990a75db5f9bd7a0ba", + "sha256:763654dc346eeb66fa726a88d27f911339950d20a25303dfc098f3b59ba26614", + "sha256:7bae4b33363f44343e0fac5004c8e44576c3ed00885be4eee1f2260802c116c3", + "sha256:8a4b8a0fd0547f3b436b548284aa604ba183bfac26f41a7ffb23d0ff5db8c658", + "sha256:8b08d68e4cb498eabf85411beda5c32e591ef8d0a6d18c948c3f80ed5d2c6e31", + "sha256:9840f27948b54aefa3c6386e5ed0f124d641eb54fa2f2bc9aebcb270598487fc", + "sha256:9ae8f822370e47486ba1880f7580669058a41e64bdaa41019f4617317489f884", + "sha256:9db49197080646a113059eba1c0758161164de1bc57315e7422bbf8c86e03dcf", + "sha256:a08d23fa3f23f13a1cc6dca3b3c431d08ae48e52384e6bf47bbefb22fde58e61", + "sha256:ac111bc47733dbfa3e34d61282c91b69b1f66800b0c72b7b86dc2534faa09bef", + "sha256:bc3c69707c0bf9308193612d34ca87249d6fc91a35ce0873102321395d39024a", + "sha256:c375759a763c1c93d5b4f36620390440d9fa6dec6fcf88bce8234701d88b339c", + "sha256:c8a5988d73ec93a54f22660b64c5f3d2018163dd9ca4a5cdde8022a7e4fcb345", + "sha256:eba2bc7c28a3b2b0a3c24caff33e4d8708db008f480b03a6ea39c28661663746", + "sha256:ee187977d587b9b81929e08022f385eb11274efd75795d59d99eb23b3fa9b055", + "sha256:f3ef7616ffc27c150ffec61ac820739495f6a9ca5d8532047102756ebb27e8d1", + "sha256:f46f223fcae09c8bf2746b4eb2f351294faae04b262429cc480d34c69b133fd9", + "sha256:fd9f6429a68a246fb466696d97d1240752c889b5bfdc219fea15ae787cf366a6" ], "index": "pypi", - "version": "==3.5.17" + "version": "==3.5.19" }, "requests": { "hashes": [ @@ -466,10 +466,10 @@ }, "shodan": { "hashes": [ - "sha256:f93b7199e89eecf5c84647f66316c2c044c3aebfc1fe4d9caa43dfda07f74c4e" + "sha256:c30baebce853ad67677bf002dde96a1ca1a9729bdd300fbb3c5e5d889547a639" ], "index": "pypi", - "version": "==1.11.1" + "version": "==1.12.1" }, "sigmatools": { "hashes": [ @@ -487,10 +487,10 @@ }, "soupsieve": { "hashes": [ - "sha256:3aef141566afd07201b525c17bfaadd07580a8066f82b57f7c9417f26adbd0a3", - "sha256:e41a65e99bd125972d84221022beb1e4b5cfc68fa12c170c39834ce32d1b294c" + "sha256:6898e82ecb03772a0d82bd0d0a10c0d6dcc342f77e0701d0ec4a8271be465ece", + "sha256:b20eff5e564529711544066d7dc0f7661df41232ae263619dede5059799cdfca" ], - "version": "==1.9" + "version": "==1.9.1" }, "sparqlwrapper": { "hashes": [ @@ -555,12 +555,12 @@ }, "vulners": { "hashes": [ - "sha256:6617d5904b5369507bc34105071d312e9e1c38d73654505e7b15b9a3f1325915", - "sha256:8b05d12a9dd7cbc07198a13281299a6e014ec348522e214b1efd097e194b7568", - "sha256:a19b02e0a112d70951e10c5abc1993f7f029234212828e1b617ab35f4e460a24" + "sha256:6506f3ad45bf3fd72f9cd1ebd5fbc13f814e7cf62faed6897e33db949fe4584a", + "sha256:a0e86015343ecf1c3313f6101567749988b5eb5299a672f19fd2974121817444", + "sha256:f243fe025a84b85bd6f37e45d6cf693e24697d30661695fe5d29b652dff6a5a1" ], "index": "pypi", - "version": "==1.4.7" + "version": "==1.4.9" }, "wand": { "hashes": [ @@ -572,10 +572,10 @@ }, "xlsxwriter": { "hashes": [ - "sha256:de9ef46088489915eaaee00c7088cff93cf613e9990b46b933c98eb46f21b47f", - "sha256:df96eafc3136d9e790e35d6725b473e46ada6f585c1f6519da69b27f5c8873f7" + "sha256:3a4e4a24a6753f046dc5a5e5bc5f443fce6a18988486885a258db6963eb54163", + "sha256:92a2ba339ca939815f0e125fcde728e94ccdb3e97e1acd3275ecf25a3cacfdc6" ], - "version": "==1.1.5" + "version": "==1.1.6" }, "yara-python": { "hashes": [ @@ -760,11 +760,11 @@ }, "pytest": { "hashes": [ - "sha256:13c5e9fb5ec5179995e9357111ab089af350d788cbc944c628f3cde72285809b", - "sha256:f21d2f1fb8200830dcbb5d8ec466a9c9120e20d8b53c7585d180125cce1d297a" + "sha256:3773f4c235918987d51daf1db66d51c99fac654c81d6f2f709a046ab446d5e5d", + "sha256:b7802283b70ca24d7119b32915efa7c409982f59913c1a6c0640aacf118b95f5" ], "index": "pypi", - "version": "==4.4.0" + "version": "==4.4.1" }, "requests": { "hashes": [ From eefa35c65db9915af7e7a47a6ca4ba80ac29742a Mon Sep 17 00:00:00 2001 From: Evert0x <35029353+Evert0x@users.noreply.github.com> Date: Thu, 18 Apr 2019 00:23:38 +0200 Subject: [PATCH 362/401] Create cuckoo_submit.py --- .../modules/expansion/cuckoo_submit.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 misp_modules/modules/expansion/cuckoo_submit.py diff --git a/misp_modules/modules/expansion/cuckoo_submit.py b/misp_modules/modules/expansion/cuckoo_submit.py new file mode 100644 index 0000000..d0cb236 --- /dev/null +++ b/misp_modules/modules/expansion/cuckoo_submit.py @@ -0,0 +1,62 @@ +import json +import requests + +misperrors = {'error': 'Error'} +mispattributes = { + 'input': ['url'], + 'output': ['text'] +} + +# possible module-types: 'expansion', 'hover' or both +moduleinfo = {'version': '1', 'author': 'Evert Kors', + 'description': 'MODULE_DESCRIPTION', + 'module-type': ['expansion', 'hover']} + +# config fields that your code expects from the site admin +moduleconfig = ['cuckoo_api'] + + +def handler(q=False): + if q is False: + return False + request = json.loads(q) + config = request.get('config') + if config is None: + misperrors['error'] = 'config is missing' + return misperrors + + cuck = config.get('cuckoo_api') + if cuck is None: + misperrors['error'] = 'cuckoo api url is missing' + return misperrors + + # The url to submit + url = request.get('url') + + HEADERS = {"Authorization": "Bearer S4MPL3"} + + urls = [ + url + ] + + try: + r = requests.post( + "%s/tasks/create/submit" % (cuck), + headers=HEADERS, + data={"strings": "\n".join(urls)} + ) + except Exception as e: + misperrors['error'] = str(e) + return misperrors + + r = {'results': [{'types': "text", 'values': "cool"}]} + return r + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From e243edb50341899a4feedbd90ec3bd4e5df5fd00 Mon Sep 17 00:00:00 2001 From: Evert0x <35029353+Evert0x@users.noreply.github.com> Date: Thu, 18 Apr 2019 14:25:05 +0200 Subject: [PATCH 363/401] Update __init__.py --- 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 42f74a3..df83ca9 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -1,6 +1,6 @@ from . import _vmray # noqa -__all__ = ['vmray_submit', 'bgpranking', 'circl_passivedns', 'circl_passivessl', +__all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'circl_passivessl', 'countrycode', 'cve', 'dns', 'btc_steroids', 'domaintools', 'eupi', 'farsight_passivedns', 'ipasn', 'passivetotal', 'sourcecache', 'virustotal', 'whois', 'shodan', 'reversedns', 'geoip_country', 'wiki', 'iprep', From 49acb5374538a4fe1bafbb4af44d422f8ca0287d Mon Sep 17 00:00:00 2001 From: Ricardo van Zutphen Date: Fri, 19 Apr 2019 14:06:35 +0200 Subject: [PATCH 364/401] Update Cuckoo module to support files and URLs --- .../modules/expansion/cuckoo_submit.py | 151 ++++++++++++++---- 1 file changed, 119 insertions(+), 32 deletions(-) diff --git a/misp_modules/modules/expansion/cuckoo_submit.py b/misp_modules/modules/expansion/cuckoo_submit.py index d0cb236..e368fbd 100644 --- a/misp_modules/modules/expansion/cuckoo_submit.py +++ b/misp_modules/modules/expansion/cuckoo_submit.py @@ -1,56 +1,143 @@ +import base64 +import io import json +import logging import requests +import sys +import urllib.parse +import zipfile -misperrors = {'error': 'Error'} +from requests.exceptions import RequestException + +log = logging.getLogger('cuckoo_submit') +log.setLevel(logging.DEBUG) +sh = logging.StreamHandler(sys.stdout) +sh.setLevel(logging.DEBUG) +fmt = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +sh.setFormatter(fmt) +log.addHandler(sh) + +moduleinfo = { + "version": "0.1", 'author': "Evert Kors", + "description": "Submit files and URLs to Cuckoo Sandbox", + "module-type": ["expansion", "hover"] +} +misperrors = {"error": "Error"} +moduleconfig = ["cuckoo_api", "api_key"] mispattributes = { - 'input': ['url'], - 'output': ['text'] + "input": ["attachment', 'malware-sample", "url", "domain"], + "output": ["text"] } -# possible module-types: 'expansion', 'hover' or both -moduleinfo = {'version': '1', 'author': 'Evert Kors', - 'description': 'MODULE_DESCRIPTION', - 'module-type': ['expansion', 'hover']} -# config fields that your code expects from the site admin -moduleconfig = ['cuckoo_api'] +class APIKeyError(RequestException): + """Raised if the Cuckoo API returns a 401. This means no or an invalid + bearer token was supplied.""" + pass + + +class CuckooAPI(object): + + def __init__(self, api_url, api_key=""): + self.api_key = api_key + if not api_url.startswith("http"): + api_url = "https://{}".format(api_url) + + self.api_url = api_url + + def _post_api(self, endpoint, files=None, data={}): + data.update({ + "owner": "MISP" + }) + + try: + response = requests.post( + urllib.parse.urljoin(self.api_url, endpoint), + files=files, data=data, + headers={"Authorization: Bearer {}".format(self.api_key)} + ) + except RequestException as e: + log.error("Failed to submit sample to Cuckoo Sandbox. %s", e) + return None + + if response.status_code == 401: + raise APIKeyError("Invalid or no Cuckoo Sandbox API key provided") + + return response.json() + + def create_task(self, filename, fp): + response = self._post_api( + "/tasks/create/file", files={"file": (filename, fp)} + ) + if not response: + return False + + return response["task_id"] + + def create_url(self, url): + response = self._post_api( + "/tasks/create/url", data={"url": url} + ) + if not response: + return False + + return response["task_id"] def handler(q=False): if q is False: return False + request = json.loads(q) - config = request.get('config') - if config is None: - misperrors['error'] = 'config is missing' + + # See if the API URL was provided. The API key is optional, as it can + # be disabled in the Cuckoo API settings. + api_url = request["config"].get("api_url") + api_key = request["config"].get("api_key", "") + if not api_url: + misperrors["error"] = "No Cuckoo API URL provided" return misperrors - cuck = config.get('cuckoo_api') - if cuck is None: - misperrors['error'] = 'cuckoo api url is missing' - return misperrors + url = request.get("url") or request.get("domain") + data = request.get("data") + filename = None + if data: + data = base64.b64decode(data) - # The url to submit - url = request.get('url') + if "malware-sample" in request: + filename = request.get("malware-sample").split("|", 1)[0] + with zipfile.ZipFile(io.BytesIO(data)) as zipf: + data = zipf.read(zipf.namelist()[0], pwd=b"infected") - HEADERS = {"Authorization": "Bearer S4MPL3"} - - urls = [ - url - ] + elif "attachment" in request: + filename = request.get("attachment") + cuckoo_api = CuckooAPI(api_url=api_url, api_key=api_key) + task_id = None try: - r = requests.post( - "%s/tasks/create/submit" % (cuck), - headers=HEADERS, - data={"strings": "\n".join(urls)} - ) - except Exception as e: - misperrors['error'] = str(e) + if url: + log.debug("Submitting URL to Cuckoo Sandbox %s", api_url) + task_id = cuckoo_api.create_url(url) + elif data and filename: + log.debug("Submitting file to Cuckoo Sandbox %s", api_url) + task_id = cuckoo_api.create_task( + filename=filename, fp=io.BytesIO(data) + ) + except APIKeyError as e: + misperrors["error"] = "Failed to submit to Cuckoo: {}".format(e) return misperrors - r = {'results': [{'types': "text", 'values': "cool"}]} - return r + if not task_id: + misperrors["error"] = "File or URL submission failed" + return misperrors + + return { + "results": [ + {"types": "text", "values": "Cuckoo task id: {}".format(task_id)} + ] + } def introspection(): From e6326185d568575a200147a2866aa8cd9769ac28 Mon Sep 17 00:00:00 2001 From: Ricardo van Zutphen Date: Fri, 19 Apr 2019 16:24:30 +0200 Subject: [PATCH 365/401] Use double quotes and provide headers correctly --- .../modules/expansion/cuckoo_submit.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/misp_modules/modules/expansion/cuckoo_submit.py b/misp_modules/modules/expansion/cuckoo_submit.py index e368fbd..c1ded90 100644 --- a/misp_modules/modules/expansion/cuckoo_submit.py +++ b/misp_modules/modules/expansion/cuckoo_submit.py @@ -9,25 +9,25 @@ import zipfile from requests.exceptions import RequestException -log = logging.getLogger('cuckoo_submit') +log = logging.getLogger("cuckoo_submit") log.setLevel(logging.DEBUG) sh = logging.StreamHandler(sys.stdout) sh.setLevel(logging.DEBUG) fmt = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) sh.setFormatter(fmt) log.addHandler(sh) moduleinfo = { - "version": "0.1", 'author': "Evert Kors", + "version": "0.1", "author": "Evert Kors", "description": "Submit files and URLs to Cuckoo Sandbox", "module-type": ["expansion", "hover"] } misperrors = {"error": "Error"} -moduleconfig = ["cuckoo_api", "api_key"] +moduleconfig = ["api_url", "api_key"] mispattributes = { - "input": ["attachment', 'malware-sample", "url", "domain"], + "input": ["attachment", "malware-sample", "url", "domain"], "output": ["text"] } @@ -56,7 +56,7 @@ class CuckooAPI(object): response = requests.post( urllib.parse.urljoin(self.api_url, endpoint), files=files, data=data, - headers={"Authorization: Bearer {}".format(self.api_key)} + headers={"Authorization": "Bearer {}".format(self.api_key)} ) except RequestException as e: log.error("Failed to submit sample to Cuckoo Sandbox. %s", e) @@ -65,6 +65,10 @@ class CuckooAPI(object): if response.status_code == 401: raise APIKeyError("Invalid or no Cuckoo Sandbox API key provided") + if response.status_code != 200: + log.error("Invalid Cuckoo API response") + return None + return response.json() def create_task(self, filename, fp): @@ -145,5 +149,5 @@ def introspection(): def version(): - moduleinfo['config'] = moduleconfig + moduleinfo["config"] = moduleconfig return moduleinfo From 7fefbd2a4c074c43b73d803f6c87491aa564996c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 19 Apr 2019 22:41:06 +0200 Subject: [PATCH 366/401] chg: Bump dependencies Fix CVE-2019-11324 (urllib3) --- Pipfile.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 428ae11..4a5c092 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -300,7 +300,7 @@ "pybgpranking": { "editable": true, "git": "https://github.com/D4-project/BGP-Ranking.git/", - "ref": "86180c080ba5f83a5160a8295ebc263709aa4eba", + "ref": "4e0741056bcc0077de1120b8724a31330b26033e", "subdirectory": "client" }, "pydnstrails": { @@ -331,13 +331,13 @@ "pyipasnhistory": { "editable": true, "git": "https://github.com/D4-project/IPASN-History.git/", - "ref": "1009124c39e9cc3b362f0f9ce4d272817b9a5186", + "ref": "c0c2bbf8d70811982dad065ea463a7e01593a38d", "subdirectory": "client" }, "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "13445b51748524195bb15aec2c1c6176bcb6bd95" + "ref": "921f414e0e026a3a4b77112012cf930242a33b04" }, "pyonyphe": { "editable": true, @@ -459,10 +459,10 @@ }, "requests-cache": { "hashes": [ - "sha256:e9270030becc739b0a7f7f834234c73a878b2d794122bf76f40055a22419eb67", - "sha256:fe561ca119879bbcfb51f03a35e35b425e18f338248e59fd5cf2166c77f457a2" + "sha256:6822f788c5ee248995c4bfbd725de2002ad710182ba26a666e85b64981866060", + "sha256:73a7211870f7d67af5fd81cad2f67cfe1cd3eb4ee6a85155e07613968cc72dfc" ], - "version": "==0.4.13" + "version": "==0.5.0" }, "shodan": { "hashes": [ @@ -494,12 +494,12 @@ }, "sparqlwrapper": { "hashes": [ - "sha256:2a95fdede2833be660b81092934c4a0054ff85f2693098556762a2759ea486f1", - "sha256:7f4c8d38ea1bfcffbc358c9a05de35a3fd7152cc3e8ea57963ee7a0a242f7a5e", - "sha256:acf6d60f0a3684cb673653b07871acb0c350a974b891f20f8ac94926ff9eb2ff" + "sha256:14ec551f0d60b4a496ffcc31f15337e844c085b8ead8cbe9a7178748a6de3794", + "sha256:21928e7a97f565e772cdeeb0abad428960f4307e3a13dbdd8f6d3da8a6a506c9", + "sha256:abc3e7eadcad32fa69a85c003853e2f6f73bda6cc999853838f401a5a1ea1109" ], "index": "pypi", - "version": "==1.8.2" + "version": "==1.8.4" }, "stix2-patterns": { "hashes": [ @@ -542,10 +542,10 @@ }, "urllib3": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", + "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" ], - "version": "==1.24.1" + "version": "==1.24.2" }, "uwhois": { "editable": true, @@ -783,10 +783,10 @@ }, "urllib3": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", + "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" ], - "version": "==1.24.1" + "version": "==1.24.2" } } } From 5367bcd409e55eff39ecc498aa28ceab39988f6d Mon Sep 17 00:00:00 2001 From: Ricardo van Zutphen Date: Mon, 22 Apr 2019 22:38:03 +0200 Subject: [PATCH 367/401] Document Cuckoo expansion module --- doc/expansion/cuckoo_submit.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 doc/expansion/cuckoo_submit.json diff --git a/doc/expansion/cuckoo_submit.json b/doc/expansion/cuckoo_submit.json new file mode 100644 index 0000000..7fe8067 --- /dev/null +++ b/doc/expansion/cuckoo_submit.json @@ -0,0 +1,9 @@ +{ + "description": "An expansion module to submit files and URLs to Cuckoo Sandbox.", + "logo": "logos/cuckoo.png", + "requirements": ["Access to a Cuckoo Sandbox API and an API key if the API requires it. (api_url and api_key)"], + "input": "A malware-sample or attachment for files. A url or domain for URLs.", + "output": "A text field containing 'Cuckoo task id: '", + "references": ["https://cuckoosandbox.org/", "https://cuckoo.sh/docs/"], + "features": "The module takes a malware-sample, attachment, url or domain and submits it to Cuckoo Sandbox.\n The returned task id can be used to retrieve results when the analysis completed." +} From cafa1a6229649358ac04e2d1326bc07747628046 Mon Sep 17 00:00:00 2001 From: Ricardo van Zutphen Date: Mon, 22 Apr 2019 22:45:38 +0200 Subject: [PATCH 368/401] Generate latest version of documentation --- doc/README.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/doc/README.md b/doc/README.md index c32a8c4..02506ab 100644 --- a/doc/README.md +++ b/doc/README.md @@ -178,6 +178,25 @@ Module to query Crowdstrike Falcon. ----- +#### [cuckoo_submit](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/cuckoo_submit.py) + + + +An expansion module to submit files and URLs to Cuckoo Sandbox. +- **features**: +>The module takes a malware-sample, attachment, url or domain and submits it to Cuckoo Sandbox. +> The returned task id can be used to retrieve results when the analysis completed. +- **input**: +>A malware-sample or attachment for files. A url or domain for URLs. +- **output**: +>A text field containing 'Cuckoo task id: ' +- **references**: +>https://cuckoosandbox.org/, https://cuckoo.sh/docs/ +- **requirements**: +>Access to a Cuckoo Sandbox API and an API key if the API requires it. (api_url and api_key) + +----- + #### [cve](https://github.com/MISP/misp-modules/tree/master/misp_modules/modules/expansion/cve.py) @@ -1081,7 +1100,13 @@ OSQuery export of a MISP event. Simple export of a MISP event to PDF. - **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. +>The module takes care of the PDF file building, and work with any MISP Event. Except the requirement of reportlab, used to create the file, there is no special feature concerning the Event. Some parameters can be given through the config dict. 'MISP_base_url_for_dynamic_link' is your MISP URL, to attach an hyperlink to your event on your MISP instance from the PDF. Keep it clear to avoid hyperlinks in the generated pdf. +> 'MISP_name_for_metadata' is your CERT or MISP instance name. Used as text in the PDF' metadata +> 'Activate_textual_description' is a boolean (True or void) to activate the textual description/header abstract of an event +> 'Activate_galaxy_description' is a boolean (True or void) to activate the description of event related galaxies. +> 'Activate_related_events' is a boolean (True or void) to activate the description of related event. Be aware this might leak information on confidential events linked to the current event ! +> 'Activate_internationalization_fonts' is a boolean (True or void) to activate Noto fonts instead of default fonts (Helvetica). This allows the support of CJK alphabet. Be sure to have followed the procedure to download Noto fonts (~70Mo) in the right place (/tools/pdf_fonts/Noto_TTF), to allow PyMisp to find and use them during PDF generation. +> 'Custom_fonts_path' is a text (path or void) to the TTF file of your choice, to create the PDF with it. Be aware the PDF won't support bold/italic/special style anymore with this option - **input**: >MISP Event - **output**: @@ -1089,7 +1114,7 @@ Simple export of a MISP event to PDF. - **references**: >https://acrobat.adobe.com/us/en/acrobat/about-adobe-pdf.html - **requirements**: ->PyMISP, asciidoctor +>PyMISP, reportlab ----- From c85ab8d93ca9485e5534cd779cc91e5ba8c9d2c8 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Tue, 23 Apr 2019 11:38:56 +0200 Subject: [PATCH 369/401] initial version of QR code reader Module accepts attachments and processes pictures. It tries to identify and analyze an existing QR code. Identified values can be inserted into the event. --- misp_modules/modules/expansion/qrcode.py | 86 ++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 misp_modules/modules/expansion/qrcode.py diff --git a/misp_modules/modules/expansion/qrcode.py b/misp_modules/modules/expansion/qrcode.py new file mode 100644 index 0000000..5357e6d --- /dev/null +++ b/misp_modules/modules/expansion/qrcode.py @@ -0,0 +1,86 @@ +import json +from pyzbar import pyzbar +import cv2 +import re +import binascii +import np + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['attachment'], + 'output': ['url', 'btc']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': 'QR code decoder', + 'module-type': ['expansion', 'hover']} + +debug = True +debug_prefix = "[DEBUG] QR Code module: " +# format example: bitcoin:1GXZ6v7FZzYBEnoRaG77SJxhu7QkvQmFuh?amount=0.15424 +# format example: http://example.com +cryptocurrencies = {'bitcoin'} +schemas = {'http://', 'https://', 'ftp://'} +moduleconfig = [] + +def handler(q=False): + if q is False: + return False + q = json.loads(q) + filename = q['attachment'] + try: + img_array = np.fromstring(binascii.a2b_base64(q['data']), np.uint8) + except: + err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" + misperrors['error'] = err + print(err) + return misperrors + image = cv2.imdecode(img_array, cv2.IMREAD_COLOR) + if q: + barcodes = pyzbar.decode(image) + for item in barcodes: + try: + result = item.data.decode() + except Exception as e: + print(e) + return + if debug: + print(debug_prefix + result) + for item in cryptocurrencies: + if item in result: + try: + currency, address, extra = re.split('\:|\?', result) + except Exception as e: + print(e) + if currency in cryptocurrencies: + try: + amount = re.split('=', extra)[1] + if debug: + print(debug_prefix + address) + print(debug_prefix + amount) + return {'results': [{'types': ['btc'], 'values': address, 'comment': "BTC: " + amount + " from file " + filename}]} + except Exception as e: + print(e) + else: + print(address) + for item in schemas: + if item in result: + try: + url = result + if debug: + print(debug_prefix + url) + return {'results': [{'types': ['url'], 'values': url, 'comment': "from QR code of file " + filename}]} + except Exception as e: + print(e) + else: + try: + return {'results': [{'types': ['text'], 'values': result, 'comment': "from QR code of file " + filename}]} + except Exception as e: + print(e) + misperrors['error'] = "Couldn't decode QR code in attachment." + return misperrors + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From d5180e7e79584cf256ad88db1422f3b155445122 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 23 Apr 2019 14:37:27 +0200 Subject: [PATCH 370/401] chg: [qrcode] various fixes to make it PEP compliant --- misp_modules/modules/expansion/qrcode.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/qrcode.py b/misp_modules/modules/expansion/qrcode.py index 5357e6d..82f4f88 100644 --- a/misp_modules/modules/expansion/qrcode.py +++ b/misp_modules/modules/expansion/qrcode.py @@ -20,6 +20,7 @@ cryptocurrencies = {'bitcoin'} schemas = {'http://', 'https://', 'ftp://'} moduleconfig = [] + def handler(q=False): if q is False: return False @@ -27,7 +28,7 @@ def handler(q=False): filename = q['attachment'] try: img_array = np.fromstring(binascii.a2b_base64(q['data']), np.uint8) - except: + except Exception as e: err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" misperrors['error'] = err print(err) @@ -46,7 +47,7 @@ def handler(q=False): for item in cryptocurrencies: if item in result: try: - currency, address, extra = re.split('\:|\?', result) + currency, address, extra = re.split('\:|\?', result) except Exception as e: print(e) if currency in cryptocurrencies: @@ -60,7 +61,7 @@ def handler(q=False): print(e) else: print(address) - for item in schemas: + for item in schemas: if item in result: try: url = result @@ -77,6 +78,7 @@ def handler(q=False): misperrors['error'] = "Couldn't decode QR code in attachment." return misperrors + def introspection(): return mispattributes From 44050ec4da5b830161b2f9397e7910309cf1bdd5 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 23 Apr 2019 14:44:00 +0200 Subject: [PATCH 371/401] chg: [qrcode] flake8 needs some drugs --- misp_modules/modules/expansion/qrcode.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/qrcode.py b/misp_modules/modules/expansion/qrcode.py index 82f4f88..437de60 100644 --- a/misp_modules/modules/expansion/qrcode.py +++ b/misp_modules/modules/expansion/qrcode.py @@ -32,6 +32,7 @@ def handler(q=False): err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" misperrors['error'] = err print(err) + print(e) return misperrors image = cv2.imdecode(img_array, cv2.IMREAD_COLOR) if q: @@ -47,7 +48,7 @@ def handler(q=False): for item in cryptocurrencies: if item in result: try: - currency, address, extra = re.split('\:|\?', result) + currency, address, extra = re.split(r'\:|\?', result) except Exception as e: print(e) if currency in cryptocurrencies: From e55ae11a1e126ad040eef1e17ae6836c770f2629 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 23 Apr 2019 14:45:12 +0200 Subject: [PATCH 372/401] chg: [qrcode] added to the __init__ --- 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 42f74a3..ff847a4 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -8,4 +8,4 @@ __all__ = ['vmray_submit', 'bgpranking', 'circl_passivedns', 'circl_passivessl', 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', - 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors'] + 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors', 'qrcode'] From 32430a15cb1794bb41596e5a4b80a2f2fce77109 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 23 Apr 2019 14:49:02 +0200 Subject: [PATCH 373/401] chg: [qrcode] add requirements --- Pipfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Pipfile b/Pipfile index 2f2d172..6a88fcc 100644 --- a/Pipfile +++ b/Pipfile @@ -42,6 +42,9 @@ misp-modules = {editable = true,path = "."} pybgpranking = {editable = true,git = "https://github.com/D4-project/BGP-Ranking.git/",subdirectory = "client"} pyipasnhistory = {editable = true,git = "https://github.com/D4-project/IPASN-History.git/",subdirectory = "client"} backscatter = "*" +pyzbar = "*" +opencv-python = "*" +np = "*" [requires] python_version = "3.6" From 5adb9bfcfab220f5db5af4f0ec89a9611984db8b Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 23 Apr 2019 14:54:05 +0200 Subject: [PATCH 374/401] chg: [doc] qrcode and Cisco FireSight added --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 18b5548..94a5778 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [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. +* [qrcode](misp_modules/modules/expansion/qrcode.py) - a module decode QR code, barcode and similar codes from an image and enrich with the decoded values. * [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/). @@ -68,6 +69,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ### Export modules * [CEF](misp_modules/modules/export_mod/cef_export.py) module to export Common Event Format (CEF). +* [Cisco FireSight Manager ACL rule](misp_modules/modules/export_mod/cisco_firesight_manager_ACL_rule_export.py) module to export as rule for the Cisco FireSight manager ACL. * [GoAML export](misp_modules/modules/export_mod/goamlexport.py) module to export in [GoAML format](http://goaml.unodc.org/goaml/en/index.html). * [Lite Export](misp_modules/modules/export_mod/liteexport.py) module to export a lite event. * [PDF export](misp_modules/modules/export_mod/pdfexport.py) module to export an event in PDF. From 8acbb1762d07669d41ab9215f563507535a288c1 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 23 Apr 2019 14:59:42 +0200 Subject: [PATCH 375/401] chg: [travis] because everyone need a bar --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index e8fea8e..b0d73ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ python: - "3.7-dev" install: + - apt-get install libzbar0 libzbar-dev - pip install pipenv - pipenv install --dev From 72cd5e3c1f0ccfae85e30f3644cef578d1239f06 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 23 Apr 2019 15:02:32 +0200 Subject: [PATCH 376/401] chg: [travis] because we all need sudo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b0d73ed..52a7742 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ python: - "3.7-dev" install: - - apt-get install libzbar0 libzbar-dev + - sudo apt-get install libzbar0 libzbar-dev - pip install pipenv - pipenv install --dev From 2d8aaf09c20849b1ce6f251dfe07417341b0d29a Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Tue, 23 Apr 2019 15:40:22 +0200 Subject: [PATCH 377/401] brackets are difficult... --- misp_modules/modules/expansion/qrcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misp_modules/modules/expansion/qrcode.py b/misp_modules/modules/expansion/qrcode.py index 437de60..9a62827 100644 --- a/misp_modules/modules/expansion/qrcode.py +++ b/misp_modules/modules/expansion/qrcode.py @@ -16,8 +16,8 @@ debug = True debug_prefix = "[DEBUG] QR Code module: " # format example: bitcoin:1GXZ6v7FZzYBEnoRaG77SJxhu7QkvQmFuh?amount=0.15424 # format example: http://example.com -cryptocurrencies = {'bitcoin'} -schemas = {'http://', 'https://', 'ftp://'} +cryptocurrencies = ['bitcoin'] +schemas = ['http://', 'https://', 'ftp://'] moduleconfig = [] From b787aa7961976f560c638db9eff3223a6be07d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Tue, 23 Apr 2019 17:02:21 +0200 Subject: [PATCH 378/401] chg: Require python3 instead of python 3.6 --- Pipfile | 2 +- Pipfile.lock | 99 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/Pipfile b/Pipfile index 6a88fcc..ba650fe 100644 --- a/Pipfile +++ b/Pipfile @@ -47,4 +47,4 @@ opencv-python = "*" np = "*" [requires] -python_version = "3.6" +python_version = "3" diff --git a/Pipfile.lock b/Pipfile.lock index 4a5c092..3191d4b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "23dec0fa6400c828e294ea9981b433903c17358ca61d7abdaec8df5a1c89f08c" + "sha256": "7fee9399d8a7151a79b6f8bbce64564062fd562b0a091fd45a875884d3fb954e" }, "pipfile-spec": 6, "requires": { - "python_version": "3.6" + "python_version": "3" }, "sources": [ { @@ -158,9 +158,10 @@ }, "httplib2": { "hashes": [ - "sha256:4ba6b8fd77d0038769bf3c33c9a96a6f752bc4cdf739701fdcaf210121f399d4" + "sha256:23914b5487dfe8ef09db6656d6d63afb0cf3054ad9ebc50868ddc8e166b5f8e8", + "sha256:a18121c7c72a56689efbf1aef990139ad940fee1e64c6f2458831736cd593600" ], - "version": "==0.12.1" + "version": "==0.12.3" }, "idna": { "hashes": [ @@ -236,6 +237,41 @@ ], "version": "==4.5.2" }, + "np": { + "hashes": [ + "sha256:781265283f3823663ad8fb48741aae62abcf4c78bc19f908f8aa7c1d3eb132f8" + ], + "index": "pypi", + "version": "==1.0.2" + }, + "numpy": { + "hashes": [ + "sha256:0e2eed77804b2a6a88741f8fcac02c5499bba3953ec9c71e8b217fad4912c56c", + "sha256:1c666f04553ef70fda54adf097dbae7080645435fc273e2397f26bbf1d127bbb", + "sha256:1f46532afa7b2903bfb1b79becca2954c0a04389d19e03dc73f06b039048ac40", + "sha256:315fa1b1dfc16ae0f03f8fd1c55f23fd15368710f641d570236f3d78af55e340", + "sha256:3d5fcea4f5ed40c3280791d54da3ad2ecf896f4c87c877b113576b8280c59441", + "sha256:48241759b99d60aba63b0e590332c600fc4b46ad597c9b0a53f350b871ef0634", + "sha256:4b4f2924b36d857cf302aec369caac61e43500c17eeef0d7baacad1084c0ee84", + "sha256:54fe3b7ed9e7eb928bbc4318f954d133851865f062fa4bbb02ef8940bc67b5d2", + "sha256:5a8f021c70e6206c317974c93eaaf9bc2b56295b6b1cacccf88846e44a1f33fc", + "sha256:754a6be26d938e6ca91942804eb209307b73f806a1721176278a6038869a1686", + "sha256:771147e654e8b95eea1293174a94f34e2e77d5729ad44aefb62fbf8a79747a15", + "sha256:78a6f89da87eeb48014ec652a65c4ffde370c036d780a995edaeb121d3625621", + "sha256:7fde5c2a3a682a9e101e61d97696687ebdba47637611378b4127fe7e47fdf2bf", + "sha256:80d99399c97f646e873dd8ce87c38cfdbb668956bbc39bc1e6cac4b515bba2a0", + "sha256:88a72c1e45a0ae24d1f249a529d9f71fe82e6fa6a3fd61414b829396ec585900", + "sha256:a4f4460877a16ac73302a9c077ca545498d9fe64e6a81398d8e1a67e4695e3df", + "sha256:a61255a765b3ac73ee4b110b28fccfbf758c985677f526c2b4b39c48cc4b509d", + "sha256:ab4896a8c910b9a04c0142871d8800c76c8a2e5ff44763513e1dd9d9631ce897", + "sha256:abbd6b1c2ef6199f4b7ca9f818eb6b31f17b73a6110aadc4e4298c3f00fab24e", + "sha256:b16d88da290334e33ea992c56492326ea3b06233a00a1855414360b77ca72f26", + "sha256:b78a1defedb0e8f6ae1eb55fa6ac74ab42acc4569c3a2eacc2a407ee5d42ebcb", + "sha256:cfef82c43b8b29ca436560d51b2251d5117818a8d1fb74a8384a83c096745dad", + "sha256:d160e57731fcdec2beda807ebcabf39823c47e9409485b5a3a1db3a8c6ce763e" + ], + "version": "==1.16.3" + }, "oauth2": { "hashes": [ "sha256:15b5c42301f46dd63113f1214b0d81a8b16254f65a86d3c32a1b52297f3266e6", @@ -244,6 +280,39 @@ "index": "pypi", "version": "==1.9.0.post1" }, + "opencv-python": { + "hashes": [ + "sha256:1703a296a96d3d46615e5053f224867977accb4240bcaa0fcabcb0768bf5ac13", + "sha256:1777ce7535ee7a1995cae168a107a1320e9df13648b930e72a1a2c2eccd64cda", + "sha256:1e5520482fb18fbd64d079e7f17ac0018f195fd75f6360a53bb82d7903106b50", + "sha256:25522dcf2529614750a71112a6659759080b4bdc2323f19d47f4d895960fd796", + "sha256:2af5f2842ad44c65ae2647377e0ff198719e1a1cfc9c6a19bc0c525c035d4bd8", + "sha256:31ec48d7eca13fc25c287dea7cecab453976e372cad8f50d55c054a247efda21", + "sha256:47cf48ff5dbd554e9f58cc9e98cf0b5de3f6a971172612bffa06bc5fb79ce872", + "sha256:494f98366bb5d6c2ac7e50e6617139f353704fd97a6d12ec9d392e72817d5cb0", + "sha256:4a9845870739e640e3350a8d98d511c92c087fe3d66090e83be7bf94e0ac64f7", + "sha256:4ac29cc0847d948a6636899014e84e165c30cc8779d6218394d44363462a01ce", + "sha256:5857ace03b7854221abf8072462d306c2c2ce4e366190b21d90ee8ee8aaf5bb4", + "sha256:5b4a23d99d5a2874767034466f5a8fd37b9f93ac14955a01b1a208983c76b9ad", + "sha256:734d87a5021c037064beb62133e135e66c7128e401a63b8b842b809ae2093749", + "sha256:78005c1c5d15ef4e32e0f485557bd15b5b6d87f49c19db7fe3e9246a61ebe7e4", + "sha256:81ae2283225c5c52fc3d72debd4241c30ccff2bb922578bf7867f9851cce3acb", + "sha256:88dbf900f297fdae0f62b899d6a784d8868ec2135854c5f8a9abbad00a6f0c5b", + "sha256:8c98ea7b8d327a31cd6028782a06147d0e0329ae8e829e881fb5d02f7ed8aec9", + "sha256:937d4686fef6967921145290f5b50c01c00c5b5d3542a6519e8a85cd88448723", + "sha256:a057958c0e362b3c4f03b9af1cbdb6d5af035fd22ecd7fd794eba8fdeb049eb8", + "sha256:c41eab31fa2c641226c6187caa391a688d064c99f078d604574f1912296b771f", + "sha256:cf4f7e62d1f80d1fa85a1693a3500def5cde54b2b75212b3609e552e4c25acfb", + "sha256:d90d60143e18334330c149f293071c9f2f3c79c896f33dc4ec65099e58baaaa7", + "sha256:db3106b7ca86999a7bd1f2fcc93e49314e5e6e451356774e421a69428df5020b", + "sha256:dbaf264db56f4771dfac6624f438bc4dc670aa94f61a6138848fcab7e9e77380", + "sha256:e65206c4cf651dc9cf0829962fae8bec986767c9f123d6a1ad17f9356bf7257e", + "sha256:eac94ddc78c58e891cff7180274317dad2938a4ddfc6ced1c04846c7f50e77e9", + "sha256:f2e828711f044a965509c862b3a59b3181e9c56c145a950cb53d43fec54e66d2" + ], + "index": "pypi", + "version": "==4.1.0.25" + }, "passivetotal": { "hashes": [ "sha256:d745a6519ec04e3a354682978ebf07778bf7602beac30307cbad075ff1a4418d" @@ -401,6 +470,15 @@ ], "version": "==5.1" }, + "pyzbar": { + "hashes": [ + "sha256:0e204b904e093e5e75aa85e0203bb0e02888105732a509b51f31cff400f34265", + "sha256:496249b546be70ec98c0ff0ad9151e73daaffff129266df86150a15dcd8dac4c", + "sha256:7d6c01d2c0a352fa994aa91b5540d1caeaeaac466656eb41468ca5df33be9f2e" + ], + "index": "pypi", + "version": "==0.1.8" + }, "rdflib": { "hashes": [ "sha256:58d5994610105a457cff7fdfe3d683d87786c5028a45ae032982498a7e913d6f", @@ -564,18 +642,19 @@ }, "wand": { "hashes": [ - "sha256:91810d241ab0851d40e67c946beb960b869c4f4160c397eac291ec6283ee3e3f", - "sha256:ae7c0958509a22f531b7b97e93adfd3f1208f0ac1c593af9e5f0cffa4ac06d5b" + "sha256:63ab24dee0264a44f5f045d4ecc0d392bc1cc195e5a2f80ce537b2c205c3033b", + "sha256:a2c318993791fab4fcfd460045415176f81d42f8c6fd8a88fb8d74d2f0f34b97", + "sha256:f68f32f2e4eca663a361d36148f06372de560442dcf8c785a53a64ee282572c9" ], "index": "pypi", - "version": "==0.5.2" + "version": "==0.5.3" }, "xlsxwriter": { "hashes": [ - "sha256:3a4e4a24a6753f046dc5a5e5bc5f443fce6a18988486885a258db6963eb54163", - "sha256:92a2ba339ca939815f0e125fcde728e94ccdb3e97e1acd3275ecf25a3cacfdc6" + "sha256:2a40b427dac0f640031e5b33abe97e761de6e0f12d4d346e7b2e2b67cf6ee927", + "sha256:431edc9ba1132eec1996939aa83fffe41885d3042ab09d47c3086f41a156c430" ], - "version": "==1.1.6" + "version": "==1.1.7" }, "yara-python": { "hashes": [ From 4631c17286dacc52c71eda700a26a2403162b85b Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Tue, 23 Apr 2019 19:49:58 +0200 Subject: [PATCH 379/401] chg: [doc] cuckoo_submit module added --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 94a5778..081120d 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,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). +* [Cuckoo submit](misp_modules/modules/expansion/cuckoo_submit.py) - A hover module to submit malware sample, url, attachment, domain to Cuckoo Sandbox. * [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. From e893a17583a36c84d8dd6deb112eaf8c41476eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Wed, 24 Apr 2019 11:29:16 +0200 Subject: [PATCH 380/401] chg: Bump dependencies, update REQUIREMENTS file --- Pipfile.lock | 8 ++++---- REQUIREMENTS | 47 +++++++++++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 3191d4b..8a073a9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -633,12 +633,12 @@ }, "vulners": { "hashes": [ - "sha256:6506f3ad45bf3fd72f9cd1ebd5fbc13f814e7cf62faed6897e33db949fe4584a", - "sha256:a0e86015343ecf1c3313f6101567749988b5eb5299a672f19fd2974121817444", - "sha256:f243fe025a84b85bd6f37e45d6cf693e24697d30661695fe5d29b652dff6a5a1" + "sha256:146ef130f215b50cdff790b06b4886c7edb325c075e9fce4bf1d3ab8d64a10d0", + "sha256:53406a86126159eaee9575fa667c99459bfdf9dd8c06bd0ce73fbe536b305e30", + "sha256:a258ccdbaee586207bc80d3590f0315ff151cfe16ea54f2e1629a6018fd9f2a3" ], "index": "pypi", - "version": "==1.4.9" + "version": "==1.5.0" }, "wand": { "hashes": [ diff --git a/REQUIREMENTS b/REQUIREMENTS index 99e1c02..b9d198a 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -1,9 +1,9 @@ -i https://pypi.org/simple -e . --e git+https://github.com/D4-project/BGP-Ranking.git/@37c97ae252ec4bf1d67733a49d4895c8cb009cf9#egg=pybgpranking&subdirectory=client --e git+https://github.com/D4-project/IPASN-History.git/@e846cd36fe1ed6b22f60890bba89f84e61b62e59#egg=pyipasnhistory&subdirectory=client +-e git+https://github.com/D4-project/BGP-Ranking.git/@4e0741056bcc0077de1120b8724a31330b26033e#egg=pybgpranking&subdirectory=client +-e git+https://github.com/D4-project/IPASN-History.git/@c0c2bbf8d70811982dad065ea463a7e01593a38d#egg=pyipasnhistory&subdirectory=client -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 --e git+https://github.com/MISP/PyMISP.git@b8759673b91e733c307698abdc0d5ed82fd7e0de#egg=pymisp +-e git+https://github.com/MISP/PyMISP.git@921f414e0e026a3a4b77112012cf930242a33b04#egg=pymisp -e git+https://github.com/Rafiot/uwhoisd.git@411572840eba4c72dc321c549b36a54ed5cea9de#egg=uwhois&subdirectory=client -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails -e git+https://github.com/sebdraven/pyonyphe@cbb0168d5cb28a9f71f7ab3773164a7039ccdb12#egg=pyonyphe @@ -16,7 +16,7 @@ beautifulsoup4==4.7.1 blockchain==1.4.4 certifi==2019.3.9 chardet==3.0.4 -click-plugins==1.0.4 +click-plugins==1.1.1 click==7.0 colorama==0.4.1 dnspython==1.16.0 @@ -24,44 +24,47 @@ domaintools-api==0.3.3 enum-compat==0.0.2 ez-setup==0.9 future==0.17.1 -httplib2==0.12.1 -idna-ssl==1.1.0 ; python_version < '3.7' +httplib2==0.12.3 idna==2.8 isodate==0.6.0 jsonschema==3.0.1 maclookup==1.0.3 multidict==4.5.2 +np==1.0.2 +numpy==1.16.3 oauth2==1.9.0.post1 +opencv-python==4.1.0.25 passivetotal==1.0.30 -pillow==5.4.1 -psutil==5.6.0 +pillow==6.0.0 +psutil==5.6.1 pyeupi==1.0 pygeoip==0.3.2 -pyparsing==2.3.1 +pyparsing==2.4.0 pypdns==1.3 pypssl==2.1 pyrsistent==0.14.11 pytesseract==0.2.6 python-dateutil==2.8.0 -pyyaml==3.13 +pyyaml==5.1 +pyzbar==0.1.8 rdflib==4.2.2 -redis==3.2.0 -reportlab==3.5.13 -requests-cache==0.4.13 +redis==3.2.1 +reportlab==3.5.19 +requests-cache==0.5.0 requests==2.21.0 -shodan==1.11.1 -sigmatools==0.9 +shodan==1.12.1 +sigmatools==0.10 six==1.12.0 -soupsieve==1.8 -sparqlwrapper==1.8.2 +soupsieve==1.9.1 +sparqlwrapper==1.8.4 stix2-patterns==1.1.0 tabulate==0.8.3 -tornado==6.0.1 +tornado==6.0.2 url-normalize==1.4.1 urlarchiver==0.2 -urllib3==1.24.1 -vulners==1.4.5 -wand==0.5.1 -xlsxwriter==1.1.5 +urllib3==1.24.2 +vulners==1.5.0 +wand==0.5.3 +xlsxwriter==1.1.7 yara-python==3.8.1 yarl==1.3.0 From 7171c8ce92432e7850b8f5d6a51420c3543a36b0 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Wed, 24 Apr 2019 13:54:21 +0200 Subject: [PATCH 381/401] initial version of OCR expansion module --- misp_modules/modules/expansion/__init__.py | 3 +- misp_modules/modules/expansion/ocr.py | 51 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 misp_modules/modules/expansion/ocr.py diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 994b289..ec78e9b 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -8,4 +8,5 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', - 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors', 'qrcode'] + 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors', + 'qrcode', 'ocr'] diff --git a/misp_modules/modules/expansion/ocr.py b/misp_modules/modules/expansion/ocr.py new file mode 100644 index 0000000..afdf343 --- /dev/null +++ b/misp_modules/modules/expansion/ocr.py @@ -0,0 +1,51 @@ +import json +import re +import binascii +import cv2 +import np +import pytesseract + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['attachment'], + 'output': ['freetext', 'text']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': 'OCR decoder', + 'module-type': ['expansion']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + q = json.loads(q) + filename = q['attachment'] + try: + img_array = np.frombuffer(binascii.a2b_base64(q['data']), np.uint8) + except Exception as e: + print(e) + err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" + misperrors['error'] = err + print(err) + return misperrors + + image = img_array + image = cv2.imdecode(img_array, cv2.IMREAD_COLOR) + try: + decoded = pytesseract.image_to_string(image) + return {'results': [{'types': ['freetext'], 'values': decoded, 'comment': "OCR from file " + filename}, + {'types': ['text'], 'values': decoded, 'comment': "ORC from file " + filename}]} + except Exception as e: + print(e) + err = "Couldn't analyze file type. Only images are supported right now." + misperrors['error'] = err + return misperrors + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 614fc1354b49bf281b746d3c6ca0bbbffcc21bff Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Wed, 24 Apr 2019 14:01:08 +0200 Subject: [PATCH 382/401] chg: [ocr] re module not used - removed --- misp_modules/modules/expansion/ocr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/misp_modules/modules/expansion/ocr.py b/misp_modules/modules/expansion/ocr.py index afdf343..cd6baca 100644 --- a/misp_modules/modules/expansion/ocr.py +++ b/misp_modules/modules/expansion/ocr.py @@ -1,5 +1,4 @@ import json -import re import binascii import cv2 import np From 81b0082ae5cb2a42303c4629dd53af954713cb25 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Wed, 24 Apr 2019 14:01:48 +0200 Subject: [PATCH 383/401] chg: [init] removed trailing whitespace --- 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 ec78e9b..03a7899 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -8,5 +8,5 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c 'yara_syntax_validator', 'hashdd', 'onyphe', 'onyphe_full', 'rbl', 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', - 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors', + 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors', 'qrcode', 'ocr'] From 5104bce4519d994aa868f9b576b803e393ca1ce7 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Wed, 24 Apr 2019 14:53:03 +0200 Subject: [PATCH 384/401] renamed module --- 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 ec78e9b..38d59ad 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -9,4 +9,4 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors', - 'qrcode', 'ocr'] + 'qrcode', 'ocr-enrich'] From 07f759b07a5994e2fea4410a4a230e2304159009 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Wed, 24 Apr 2019 14:53:16 +0200 Subject: [PATCH 385/401] renamed file --- misp_modules/modules/expansion/{ocr.py => ocr-enrich.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename misp_modules/modules/expansion/{ocr.py => ocr-enrich.py} (100%) diff --git a/misp_modules/modules/expansion/ocr.py b/misp_modules/modules/expansion/ocr-enrich.py similarity index 100% rename from misp_modules/modules/expansion/ocr.py rename to misp_modules/modules/expansion/ocr-enrich.py From 29e57dfcc6afa59b3d68d5ccb3c65ee649001a09 Mon Sep 17 00:00:00 2001 From: Steve Clement Date: Thu, 25 Apr 2019 17:36:32 +0900 Subject: [PATCH 386/401] chg: [doc] Added new dependencies and updated RHEL/CentOS howto. --- README.md | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 169151e..64d0960 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ## How to install and start MISP modules in a Python virtualenv? (recommended) ~~~~bash -sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick virtualenv +sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick virtualenv libopencv-dev zbar-tools sudo -u www-data virtualenv -p python3 /var/www/MISP/venv cd /usr/local/src/ sudo git clone https://github.com/MISP/misp-modules.git @@ -107,36 +107,24 @@ sudo systemctl enable --now misp-modules /var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules ~~~~ -## How to install and start MISP modules on Debian-based distributions ? - -~~~~bash -sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick -cd /usr/local/src/ -sudo git clone https://github.com/MISP/misp-modules.git -cd misp-modules -sudo pip3 install -I -r REQUIREMENTS -sudo pip3 install -I . -# Start misp-modules as a service -sudo cp etc/systemd/system/misp-modules.service /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable --now misp-modules -/var/www/MISP/venv/bin/misp-modules -l 127.0.0.1 -s & #to start the modules -~~~~ - ## How to install and start MISP modules on RHEL-based distributions ? As of this writing, the official RHEL repositories only contain Ruby 2.0.0 and Ruby 2.1 or higher is required. As such, this guide installs Ruby 2.2 from the [SCL](https://access.redhat.com/documentation/en-us/red_hat_software_collections/3/html/3.2_release_notes/chap-installation#sect-Installation-Subscribe) repository. + ~~~~bash -yum install rh-ruby22 +sudo yum install rh-ruby22 +sudo yum install openjpeg-devel +sudo yum install rubygem-rouge rubygem-asciidoctor zbar-devel opencv-devel cd /var/www/MISP git clone https://github.com/MISP/misp-modules.git cd misp-modules -scl enable rh-python36 ‘python3 –m pip install cryptography’ -scl enable rh-python36 ‘python3 –m pip install -I -r REQUIREMENTS’ -scl enable rh-python36 ‘python3 –m pip install –I .’ +sudo -u apache /usr/bin/scl enable rh-python36 "virtualenv -p python3 /var/www/MISP/venv" +sudo -u apache /var/www/MISP/venv/bin/pip install -U -I -r REQUIREMENTS +sudo -u apache /var/www/MISP/venv/bin/pip install -U . ~~~~ + Create the service file /etc/systemd/system/misp-modules.service : ~~~~ -[Unit] +echo "[Unit] Description=MISP's modules After=misp-workers.service @@ -144,15 +132,16 @@ After=misp-workers.service Type=simple User=apache Group=apache -ExecStart=/usr/bin/scl enable rh-python36 rh-ruby22 ‘/opt/rh/rh-python36/root/bin/misp-modules –l 127.0.0.1 –s’ +ExecStart=/usr/bin/scl enable rh-python36 rh-ruby22 '/opt/rh/rh-python36/root/bin/misp-modules –l 127.0.0.1 –s' Restart=always RestartSec=10 [Install] -WantedBy=multi-user.target +WantedBy=multi-user.target" | sudo tee /etc/systemd/system/misp-modules.service ~~~~ + The `After=misp-workers.service` must be changed or removed if you have not created a misp-workers service. -Then, enable the misp-modules service and start it ; +Then, enable the misp-modules service and start it: ~~~~bash systemctl daemon-reload systemctl enable --now misp-modules From 2c64e5ca67ee37e9d18131a8ef1e8b4c50f360b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 26 Apr 2019 11:35:03 +0200 Subject: [PATCH 387/401] fix: CTRL+C is working again Fix #292 --- misp_modules/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/misp_modules/__init__.py b/misp_modules/__init__.py index d933dc9..68a2c69 100644 --- a/misp_modules/__init__.py +++ b/misp_modules/__init__.py @@ -55,7 +55,7 @@ log = logging.getLogger('misp-modules') def handle_signal(sig, frame): - IOLoop.instance().add_callback(IOLoop.instance().stop) + IOLoop.instance().add_callback_from_signal(IOLoop.instance().stop) def init_logger(level=False): @@ -266,8 +266,11 @@ def main(): if args.t: log.info('MISP modules started in test-mode, quitting immediately.') sys.exit() - IOLoop.instance().start() - IOLoop.instance().stop() + try: + IOLoop.instance().start() + finally: + IOLoop.instance().stop() + return 0 From c825cabbbe104fe3b3151f98047c1942a1825079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 26 Apr 2019 11:40:25 +0200 Subject: [PATCH 388/401] chg: Bump dependencies --- Pipfile.lock | 91 ++++++++++++++++++++++++---------------------------- REQUIREMENTS | 8 ++--- 2 files changed, 46 insertions(+), 53 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 8a073a9..5d395d5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -170,13 +170,6 @@ ], "version": "==2.8" }, - "idna-ssl": { - "hashes": [ - "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c" - ], - "markers": "python_version < '3.7'", - "version": "==1.1.0" - }, "isodate": { "hashes": [ "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", @@ -354,17 +347,17 @@ }, "psutil": { "hashes": [ - "sha256:23e9cd90db94fbced5151eaaf9033ae9667c033dffe9e709da761c20138d25b6", - "sha256:27858d688a58cbfdd4434e1c40f6c79eb5014b709e725c180488ccdf2f721729", - "sha256:354601a1d1a1322ae5920ba397c58d06c29728a15113598d1a8158647aaa5385", - "sha256:9c3a768486194b4592c7ae9374faa55b37b9877fd9746fb4028cb0ac38fd4c60", - "sha256:c1fd45931889dc1812ba61a517630d126f6185f688eac1693171c6524901b7de", - "sha256:d463a142298112426ebd57351b45c39adb41341b91f033aa903fa4c6f76abecc", - "sha256:e1494d20ffe7891d07d8cb9a8b306c1a38d48b13575265d090fc08910c56d474", - "sha256:ec4b4b638b84d42fc48139f9352f6c6587ee1018d55253542ee28db7480cc653", - "sha256:fa0a570e0a30b9dd618bffbece590ae15726b47f9f1eaf7518dfb35f4d7dcd21" + "sha256:206eb909aa8878101d0eca07f4b31889c748f34ed6820a12eb3168c7aa17478e", + "sha256:649f7ffc02114dced8fbd08afcd021af75f5f5b2311bc0e69e53e8f100fe296f", + "sha256:6ebf2b9c996bb8c7198b385bade468ac8068ad8b78c54a58ff288cd5f61992c7", + "sha256:753c5988edc07da00dafd6d3d279d41f98c62cd4d3a548c4d05741a023b0c2e7", + "sha256:76fb0956d6d50e68e3f22e7cc983acf4e243dc0fcc32fd693d398cb21c928802", + "sha256:828e1c3ca6756c54ac00f1427fdac8b12e21b8a068c3bb9b631a1734cada25ed", + "sha256:a4c62319ec6bf2b3570487dd72d471307ae5495ce3802c1be81b8a22e438b4bc", + "sha256:acba1df9da3983ec3c9c963adaaf530fcb4be0cd400a8294f1ecc2db56499ddd", + "sha256:ef342cb7d9b60e6100364f50c57fa3a77d02ff8665d5b956746ac01901247ac4" ], - "version": "==5.6.1" + "version": "==5.6.2" }, "pybgpranking": { "editable": true, @@ -406,7 +399,7 @@ "pymisp": { "editable": true, "git": "https://github.com/MISP/PyMISP.git", - "ref": "921f414e0e026a3a4b77112012cf930242a33b04" + "ref": "582dda0ce2a8ca8e1dd2cf3842e0491caca51c62" }, "pyonyphe": { "editable": true, @@ -436,9 +429,9 @@ }, "pyrsistent": { "hashes": [ - "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2" + "sha256:5403d37f4d55ff4572b5b5676890589f367a9569529c6f728c11046c4ea4272b" ], - "version": "==0.14.11" + "version": "==0.15.1" }, "pytesseract": { "hashes": [ @@ -495,37 +488,37 @@ }, "reportlab": { "hashes": [ - "sha256:1c228a3ac2c405f7fc16eac43ba92aec448bc25438902f30590ad021e8828097", - "sha256:2210fafd3bb06308a84876fe6d19172b645373edce2b6d7501378cb9c768f825", - "sha256:232fb2037b7c3df259685f1c5ecb7826f55742dc81f0713837b84a152307483e", - "sha256:2c4f25e63fa75f3064871cf435696a4e19b7bd4901d922b766ae58a447b5b6da", - "sha256:47951166d897b60e9e7ca349db82a2b689e6478ac6078e2c7c88ca8becbb0c7d", - "sha256:526ab1193ea8e97c4838135917890e66de5f777d04283008007229b139f3c094", - "sha256:5a9cc8470623ec5b76c7e59f56b7d1fcf0254896cd61842dbdbd278934cc50f4", - "sha256:5ddc1a4a74f225e35a7f60e2eae10de6878dddc9960dad2d9cadc49092f8850d", - "sha256:6b594f6d7d71bc5778e19adb1c699a598c69b9a7bcf97fa638d8762279f9d80a", - "sha256:6e8c89b46cfaf9ae40b7db87e9f29c9e5d32d18d25f9cd10d423a5241e8ec453", - "sha256:71f4f3e3975b91ddbfc1b36a537b46d07533ca7f31945e990a75db5f9bd7a0ba", - "sha256:763654dc346eeb66fa726a88d27f911339950d20a25303dfc098f3b59ba26614", - "sha256:7bae4b33363f44343e0fac5004c8e44576c3ed00885be4eee1f2260802c116c3", - "sha256:8a4b8a0fd0547f3b436b548284aa604ba183bfac26f41a7ffb23d0ff5db8c658", - "sha256:8b08d68e4cb498eabf85411beda5c32e591ef8d0a6d18c948c3f80ed5d2c6e31", - "sha256:9840f27948b54aefa3c6386e5ed0f124d641eb54fa2f2bc9aebcb270598487fc", - "sha256:9ae8f822370e47486ba1880f7580669058a41e64bdaa41019f4617317489f884", - "sha256:9db49197080646a113059eba1c0758161164de1bc57315e7422bbf8c86e03dcf", - "sha256:a08d23fa3f23f13a1cc6dca3b3c431d08ae48e52384e6bf47bbefb22fde58e61", - "sha256:ac111bc47733dbfa3e34d61282c91b69b1f66800b0c72b7b86dc2534faa09bef", - "sha256:bc3c69707c0bf9308193612d34ca87249d6fc91a35ce0873102321395d39024a", - "sha256:c375759a763c1c93d5b4f36620390440d9fa6dec6fcf88bce8234701d88b339c", - "sha256:c8a5988d73ec93a54f22660b64c5f3d2018163dd9ca4a5cdde8022a7e4fcb345", - "sha256:eba2bc7c28a3b2b0a3c24caff33e4d8708db008f480b03a6ea39c28661663746", - "sha256:ee187977d587b9b81929e08022f385eb11274efd75795d59d99eb23b3fa9b055", - "sha256:f3ef7616ffc27c150ffec61ac820739495f6a9ca5d8532047102756ebb27e8d1", - "sha256:f46f223fcae09c8bf2746b4eb2f351294faae04b262429cc480d34c69b133fd9", - "sha256:fd9f6429a68a246fb466696d97d1240752c889b5bfdc219fea15ae787cf366a6" + "sha256:13714baa9753bfca94df67716cccb3eedcaaa30cf7bc40b282d338a718e0b610", + "sha256:16c1bb717a1a0e2ed065aa31eb5968dc03b34b728926216ef282cefeebf50c1b", + "sha256:2d9d66770880e8d112b6b925458593d34b84947c355847578cd974df0a3e3b8b", + "sha256:3334a30e477e1dfa0276eb41ed5bfd2a684c9917e55c6acb30d91abac46555f6", + "sha256:33796ea88d20c05958903c11ff34d896e462381f4a0f550854aabe6dd07cc189", + "sha256:5184f53c0babeedb4ebe297eb97794822cb122456ca03411c68256730c998d48", + "sha256:53589c5db35041920cd7a92a171506ff4eb5542ab8415af272fe4558927399a8", + "sha256:58ba0a9ca51d666d55ec7ecd83ab14763b79e7e5e0775b7717694e94c2fbbf18", + "sha256:6998652beba357da9687eba13b46ceccd0a7a2153d656cf8a03b7494c915e077", + "sha256:6c3b07c8a94ee9609c31a51e4131891c8330ffd379db23ab582fd225a06a4e59", + "sha256:7b248d2d9d4ab6d4cad91eb2b153b2c4c7b3fced89cb5a5b5bfbc7d09593871a", + "sha256:81d991c9994a576ea053b281b8c9afc28b12261197d478e72055d381f60fa26f", + "sha256:8a9a8be6841b88b13aa9c0f7d193c6d24b04b10c2e7cbf6657b1807bac5b1e9f", + "sha256:8de3107436e68014890adcec446207fd98d60c26e7feae6f550eea9eab3a622d", + "sha256:90f85afb68f7cd4fd8681af3123d23488274e4d1c3fea8d3831ef7257d9733c8", + "sha256:94857052c951ffa56de95cfce483fdf3de19587db4b1bc4f6a4043fb1c4af772", + "sha256:a47603d9b975e8870ed30ade22db3478b030dd7a6041c8272c3719d9bbeaef34", + "sha256:a5671b152d3f85963d8450e956ddecfb5d30af62dd1f73207fab9aa32a0240d2", + "sha256:a745cd1a4368fac093deff3b65614f052eced6afa9ed7fe223da2a52813f2e23", + "sha256:af454e8e844e3eeace5aead02701748b2a908f3e8cbc386cc5ddc185cef8c57f", + "sha256:c3c6c1234eed451111e969c776688e866554cb362250b88f782ab80ea62f9114", + "sha256:cc1cf8ba1b2f1669f5d873a7cfdb9e07a920243d74a66a78f8afa2cf78587864", + "sha256:cce3c9b0e115ea5553615a647b6644e5724bdc776e778593ffa5f383d907afb2", + "sha256:d137feacef83627d10adb869aa6998f29eb7af4cff3101c9fc94a1d73943b6cc", + "sha256:d7213814d050ca3b0bf7e018f94ed947e90477cd36aff298ff5932b849a0f36a", + "sha256:e381d08675807d2bb465717f69818040173351650af82730d721ecad429279a6", + "sha256:e39c6efdd64027be56ce991f7ffb86c7cee47da8c844c3544bbd68ef842831a0", + "sha256:f8526cfbbad599d22de6eb59b5a43610ba9b28f74ac2406125fe803f31a262a6" ], "index": "pypi", - "version": "==3.5.19" + "version": "==3.5.20" }, "requests": { "hashes": [ diff --git a/REQUIREMENTS b/REQUIREMENTS index b9d198a..37c5a68 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -3,7 +3,7 @@ -e git+https://github.com/D4-project/BGP-Ranking.git/@4e0741056bcc0077de1120b8724a31330b26033e#egg=pybgpranking&subdirectory=client -e git+https://github.com/D4-project/IPASN-History.git/@c0c2bbf8d70811982dad065ea463a7e01593a38d#egg=pyipasnhistory&subdirectory=client -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 --e git+https://github.com/MISP/PyMISP.git@921f414e0e026a3a4b77112012cf930242a33b04#egg=pymisp +-e git+https://github.com/MISP/PyMISP.git@582dda0ce2a8ca8e1dd2cf3842e0491caca51c62#egg=pymisp -e git+https://github.com/Rafiot/uwhoisd.git@411572840eba4c72dc321c549b36a54ed5cea9de#egg=uwhois&subdirectory=client -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails -e git+https://github.com/sebdraven/pyonyphe@cbb0168d5cb28a9f71f7ab3773164a7039ccdb12#egg=pyonyphe @@ -36,20 +36,20 @@ oauth2==1.9.0.post1 opencv-python==4.1.0.25 passivetotal==1.0.30 pillow==6.0.0 -psutil==5.6.1 +psutil==5.6.2 pyeupi==1.0 pygeoip==0.3.2 pyparsing==2.4.0 pypdns==1.3 pypssl==2.1 -pyrsistent==0.14.11 +pyrsistent==0.15.1 pytesseract==0.2.6 python-dateutil==2.8.0 pyyaml==5.1 pyzbar==0.1.8 rdflib==4.2.2 redis==3.2.1 -reportlab==3.5.19 +reportlab==3.5.20 requests-cache==0.5.0 requests==2.21.0 shodan==1.12.1 From f55d7946df192032f6b8b8a7a0e78efd30547f14 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Fri, 26 Apr 2019 12:07:55 +0200 Subject: [PATCH 389/401] introduction of new modules --- misp_modules/modules/expansion/docx-enrich.py | 61 +++++++++++++++++++ misp_modules/modules/expansion/ods-enrich.py | 56 +++++++++++++++++ misp_modules/modules/expansion/odt-enrich.py | 51 ++++++++++++++++ misp_modules/modules/expansion/pdf-enrich.py | 50 +++++++++++++++ misp_modules/modules/expansion/pptx-enrich.py | 55 +++++++++++++++++ misp_modules/modules/expansion/xlsx-enrich.py | 53 ++++++++++++++++ 6 files changed, 326 insertions(+) create mode 100644 misp_modules/modules/expansion/docx-enrich.py create mode 100644 misp_modules/modules/expansion/ods-enrich.py create mode 100644 misp_modules/modules/expansion/odt-enrich.py create mode 100644 misp_modules/modules/expansion/pdf-enrich.py create mode 100644 misp_modules/modules/expansion/pptx-enrich.py create mode 100644 misp_modules/modules/expansion/xlsx-enrich.py diff --git a/misp_modules/modules/expansion/docx-enrich.py b/misp_modules/modules/expansion/docx-enrich.py new file mode 100644 index 0000000..6380557 --- /dev/null +++ b/misp_modules/modules/expansion/docx-enrich.py @@ -0,0 +1,61 @@ +import json +import binascii +import np +import docx +import io + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['attachment'], + 'output': ['freetext', 'text']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': '.docx to freetext-import IOC extractor', + 'module-type': ['expansion']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + q = json.loads(q) + filename = q['attachment'] + try: + docx_array = np.frombuffer(binascii.a2b_base64(q['data']), np.uint8) + except Exception as e: + print(e) + err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" + misperrors['error'] = err + print(err) + return misperrors + + doc_content = "" + doc_file = io.BytesIO(docx_array) + try: + doc = docx.Document(doc_file) + for para in doc.paragraphs: + print(para.text) + doc_content = doc_content + "\n" + para.text + tables = doc.tables + for table in tables: + for row in table.rows: + for cell in row.cells: + for para in cell.paragraphs: + print(para.text) + doc_content = doc_content + "\n" + para.text + print(doc_content) + return {'results': [{'types': ['freetext'], 'values': doc_content, 'comment': ".docx-to-text from file " + filename}, + {'types': ['text'], 'values': doc_content, 'comment': ".docx-to-text from file " + filename}]} + except Exception as e: + print(e) + err = "Couldn't analyze file as .docx. Error was: " + str(e) + misperrors['error'] = err + return misperrors + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/expansion/ods-enrich.py b/misp_modules/modules/expansion/ods-enrich.py new file mode 100644 index 0000000..06d7ff5 --- /dev/null +++ b/misp_modules/modules/expansion/ods-enrich.py @@ -0,0 +1,56 @@ +import json +import binascii +import np +import ezodf +import pandas_ods_reader +import io + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['attachment'], + 'output': ['freetext', 'text']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': '.ods to freetext-import IOC extractor', + 'module-type': ['expansion']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + q = json.loads(q) + filename = q['attachment'] + try: + ods_array = np.frombuffer(binascii.a2b_base64(q['data']), np.uint8) + except Exception as e: + print(e) + err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" + misperrors['error'] = err + print(err) + return misperrors + + ods_content = "" + ods_file = io.BytesIO(ods_array) + doc = ezodf.opendoc(ods_file) + num_sheets = len(doc.sheets) + try: + for i in range(0, num_sheets): + ods = pandas_ods_reader.read_ods(ods_file, i, headers=False) + ods_content = ods_content + "\n" + ods.to_string(max_rows=None) + print(ods_content) + return {'results': [{'types': ['freetext'], 'values': ods_content, 'comment': ".ods-to-text from file " + filename}, + {'types': ['text'], 'values': ods_content, 'comment': ".ods-to-text from file " + filename}]} + except Exception as e: + print(e) + err = "Couldn't analyze file as .ods. Error was: " + str(e) + misperrors['error'] = err + return misperrors + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/expansion/odt-enrich.py b/misp_modules/modules/expansion/odt-enrich.py new file mode 100644 index 0000000..6dac555 --- /dev/null +++ b/misp_modules/modules/expansion/odt-enrich.py @@ -0,0 +1,51 @@ +import json +import binascii +import np +from ODTReader.odtreader import odtToText +import io + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['attachment'], + 'output': ['freetext', 'text']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': '.odt to freetext-import IOC extractor', + 'module-type': ['expansion']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + q = json.loads(q) + filename = q['attachment'] + try: + odt_array = np.frombuffer(binascii.a2b_base64(q['data']), np.uint8) + except Exception as e: + print(e) + err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" + misperrors['error'] = err + print(err) + return misperrors + + odt_content = "" + odt_file = io.BytesIO(odt_array) + try: + odt_content = odtToText(odt_file) + print(odt_content) + return {'results': [{'types': ['freetext'], 'values': odt_content, 'comment': ".odt-to-text from file " + filename}, + {'types': ['text'], 'values': odt_content, 'comment': ".odt-to-text from file " + filename}]} + except Exception as e: + print(e) + err = "Couldn't analyze file as .odt. Error was: " + str(e) + misperrors['error'] = err + return misperrors + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/expansion/pdf-enrich.py b/misp_modules/modules/expansion/pdf-enrich.py new file mode 100644 index 0000000..aeb5d9b --- /dev/null +++ b/misp_modules/modules/expansion/pdf-enrich.py @@ -0,0 +1,50 @@ +import json +import binascii +import np +import pytesseract +import pdftotext +import io +import collections + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['attachment'], + 'output': ['freetext', 'text']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': 'PDF to freetext-import IOC extractor', + 'module-type': ['expansion']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + q = json.loads(q) + filename = q['attachment'] + try: + pdf_array = np.frombuffer(binascii.a2b_base64(q['data']), np.uint8) + except Exception as e: + print(e) + err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" + misperrors['error'] = err + print(err) + return misperrors + + pdf_file = io.BytesIO(pdf_array) + try: + pdf_content = "\n\n".join(pdftotext.PDF(pdf_file)) + return {'results': [{'types': ['freetext'], 'values': pdf_content, 'comment': "PDF-to-text from file " + filename}]} + except Exception as e: + print(e) + err = "Couldn't analyze file as PDF. Error was: " + str(e) + misperrors['error'] = err + return misperrors + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/expansion/pptx-enrich.py b/misp_modules/modules/expansion/pptx-enrich.py new file mode 100644 index 0000000..9381262 --- /dev/null +++ b/misp_modules/modules/expansion/pptx-enrich.py @@ -0,0 +1,55 @@ +import json +import binascii +import np +from pptx import Presentation +import io + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['attachment'], + 'output': ['freetext', 'text']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': '.pptx to freetext-import IOC extractor', + 'module-type': ['expansion']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + q = json.loads(q) + filename = q['attachment'] + try: + pptx_array = np.frombuffer(binascii.a2b_base64(q['data']), np.uint8) + except Exception as e: + print(e) + err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" + misperrors['error'] = err + print(err) + return misperrors + + ppt_content = "" + ppt_file = io.BytesIO(pptx_array) + try: + ppt = Presentation(ppt_file) + for slide in ppt.slides: + for shape in slide.shapes: + if hasattr(shape, "text"): + print(shape.text) + ppt_content = ppt_content + "\n" + shape.text + return {'results': [{'types': ['freetext'], 'values': ppt_content, 'comment': ".pptx-to-text from file " + filename}, + {'types': ['text'], 'values': ppt_content, 'comment': ".pptx-to-text from file " + filename}]} + except Exception as e: + print(e) + err = "Couldn't analyze file as .pptx. Error was: " + str(e) + misperrors['error'] = err + return misperrors + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo diff --git a/misp_modules/modules/expansion/xlsx-enrich.py b/misp_modules/modules/expansion/xlsx-enrich.py new file mode 100644 index 0000000..66df3e1 --- /dev/null +++ b/misp_modules/modules/expansion/xlsx-enrich.py @@ -0,0 +1,53 @@ +import json +import binascii +import np +import pandas +import io + +misperrors = {'error': 'Error'} +mispattributes = {'input': ['attachment'], + 'output': ['freetext', 'text']} +moduleinfo = {'version': '0.1', 'author': 'Sascha Rommelfangen', + 'description': '.xlsx to freetext-import IOC extractor', + 'module-type': ['expansion']} + +moduleconfig = [] + + +def handler(q=False): + if q is False: + return False + q = json.loads(q) + filename = q['attachment'] + try: + xlsx_array = np.frombuffer(binascii.a2b_base64(q['data']), np.uint8) + except Exception as e: + print(e) + err = "Couldn't fetch attachment (JSON 'data' is empty). Are you using the 'Query enrichment' action?" + misperrors['error'] = err + print(err) + return misperrors + + xls_content = "" + xls_file = io.BytesIO(xlsx_array) + pandas.set_option('display.max_colwidth', -1) + try: + xls = pandas.read_excel(xls_file) + xls_content = xls.to_string(max_rows=None) + print(xls_content) + return {'results': [{'types': ['freetext'], 'values': xls_content, 'comment': ".xlsx-to-text from file " + filename}, + {'types': ['text'], 'values': xls_content, 'comment': ".xlsx-to-text from file " + filename}]} + except Exception as e: + print(e) + err = "Couldn't analyze file as .xlsx. Error was: " + str(e) + misperrors['error'] = err + return misperrors + + +def introspection(): + return mispattributes + + +def version(): + moduleinfo['config'] = moduleconfig + return moduleinfo From 3c0319b8bc80a60e2e33d0aebd56a2036d98d791 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Fri, 26 Apr 2019 12:08:52 +0200 Subject: [PATCH 390/401] new requirements for new modules --- .travis.yml | 2 +- Pipfile | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 52a7742..18c02c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ python: - "3.7-dev" install: - - sudo apt-get install libzbar0 libzbar-dev + - sudo apt-get install libzbar0 libzbar-dev libpoppler-cpp-dev - pip install pipenv - pipenv install --dev diff --git a/Pipfile b/Pipfile index ba650fe..8529abc 100644 --- a/Pipfile +++ b/Pipfile @@ -45,6 +45,16 @@ backscatter = "*" pyzbar = "*" opencv-python = "*" np = "*" +ODTReader = {editable = true,git = "https://github.com/cartertemm/ODTReader.git"} +python-pptx = "*" +collections = "*" +python-docx = "*" +ezodf = "*" +pandas = "*" +pandas_ods_reader = "*" +pdftotext = "*" +lxml = "*" +xlrd = "*" [requires] python_version = "3" From 1d4f8a6989b2741ae0f58d11f960b0f8aa0dafcf Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Fri, 26 Apr 2019 12:09:16 +0200 Subject: [PATCH 391/401] new modules added --- misp_modules/modules/expansion/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misp_modules/modules/expansion/__init__.py b/misp_modules/modules/expansion/__init__.py index 5c3edee..c4b2fdc 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -9,4 +9,5 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors', - 'qrcode', 'ocr-enrich'] + 'qrcode', 'ocr-enrich', 'pdf-enrich', 'docx-enrich', 'xlsx-enrich', 'pptx-enrich', + 'ods-enrich', 'odt-enrich'] From fc339c888db7cb844ce8233129c97c1e41ec7664 Mon Sep 17 00:00:00 2001 From: Sascha Rommelfangen Date: Fri, 26 Apr 2019 12:14:56 +0200 Subject: [PATCH 392/401] removed trailing whitespaces --- misp_modules/modules/expansion/docx-enrich.py | 2 +- misp_modules/modules/expansion/ods-enrich.py | 4 ++-- misp_modules/modules/expansion/odt-enrich.py | 2 +- misp_modules/modules/expansion/pdf-enrich.py | 2 +- misp_modules/modules/expansion/pptx-enrich.py | 2 +- misp_modules/modules/expansion/xlsx-enrich.py | 6 +++--- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/expansion/docx-enrich.py b/misp_modules/modules/expansion/docx-enrich.py index 6380557..d5da3f8 100644 --- a/misp_modules/modules/expansion/docx-enrich.py +++ b/misp_modules/modules/expansion/docx-enrich.py @@ -28,7 +28,7 @@ def handler(q=False): print(err) return misperrors - doc_content = "" + doc_content = "" doc_file = io.BytesIO(docx_array) try: doc = docx.Document(doc_file) diff --git a/misp_modules/modules/expansion/ods-enrich.py b/misp_modules/modules/expansion/ods-enrich.py index 06d7ff5..b247c44 100644 --- a/misp_modules/modules/expansion/ods-enrich.py +++ b/misp_modules/modules/expansion/ods-enrich.py @@ -29,14 +29,14 @@ def handler(q=False): print(err) return misperrors - ods_content = "" + ods_content = "" ods_file = io.BytesIO(ods_array) doc = ezodf.opendoc(ods_file) num_sheets = len(doc.sheets) try: for i in range(0, num_sheets): ods = pandas_ods_reader.read_ods(ods_file, i, headers=False) - ods_content = ods_content + "\n" + ods.to_string(max_rows=None) + ods_content = ods_content + "\n" + ods.to_string(max_rows=None) print(ods_content) return {'results': [{'types': ['freetext'], 'values': ods_content, 'comment': ".ods-to-text from file " + filename}, {'types': ['text'], 'values': ods_content, 'comment': ".ods-to-text from file " + filename}]} diff --git a/misp_modules/modules/expansion/odt-enrich.py b/misp_modules/modules/expansion/odt-enrich.py index 6dac555..c4513ae 100644 --- a/misp_modules/modules/expansion/odt-enrich.py +++ b/misp_modules/modules/expansion/odt-enrich.py @@ -28,7 +28,7 @@ def handler(q=False): print(err) return misperrors - odt_content = "" + odt_content = "" odt_file = io.BytesIO(odt_array) try: odt_content = odtToText(odt_file) diff --git a/misp_modules/modules/expansion/pdf-enrich.py b/misp_modules/modules/expansion/pdf-enrich.py index aeb5d9b..59d003e 100644 --- a/misp_modules/modules/expansion/pdf-enrich.py +++ b/misp_modules/modules/expansion/pdf-enrich.py @@ -2,7 +2,7 @@ import json import binascii import np import pytesseract -import pdftotext +import pdftotext import io import collections diff --git a/misp_modules/modules/expansion/pptx-enrich.py b/misp_modules/modules/expansion/pptx-enrich.py index 9381262..816e439 100644 --- a/misp_modules/modules/expansion/pptx-enrich.py +++ b/misp_modules/modules/expansion/pptx-enrich.py @@ -28,7 +28,7 @@ def handler(q=False): print(err) return misperrors - ppt_content = "" + ppt_content = "" ppt_file = io.BytesIO(pptx_array) try: ppt = Presentation(ppt_file) diff --git a/misp_modules/modules/expansion/xlsx-enrich.py b/misp_modules/modules/expansion/xlsx-enrich.py index 66df3e1..6e0ee73 100644 --- a/misp_modules/modules/expansion/xlsx-enrich.py +++ b/misp_modules/modules/expansion/xlsx-enrich.py @@ -1,7 +1,7 @@ import json import binascii import np -import pandas +import pandas import io misperrors = {'error': 'Error'} @@ -28,12 +28,12 @@ def handler(q=False): print(err) return misperrors - xls_content = "" + xls_content = "" xls_file = io.BytesIO(xlsx_array) pandas.set_option('display.max_colwidth', -1) try: xls = pandas.read_excel(xls_file) - xls_content = xls.to_string(max_rows=None) + xls_content = xls.to_string(max_rows=None) print(xls_content) return {'results': [{'types': ['freetext'], 'values': xls_content, 'comment': ".xlsx-to-text from file " + filename}, {'types': ['text'], 'values': xls_content, 'comment': ".xlsx-to-text from file " + filename}]} From 73067c8b238b563a11721281a3d58c96a678694b Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Fri, 26 Apr 2019 13:28:16 +0200 Subject: [PATCH 393/401] chg: [Pipfile] collection removed --- Pipfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Pipfile b/Pipfile index 8529abc..16ac02e 100644 --- a/Pipfile +++ b/Pipfile @@ -47,7 +47,6 @@ opencv-python = "*" np = "*" ODTReader = {editable = true,git = "https://github.com/cartertemm/ODTReader.git"} python-pptx = "*" -collections = "*" python-docx = "*" ezodf = "*" pandas = "*" From 63c12f34e6eb5b3e1998eec4dd762a1dc7a82beb Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Fri, 26 Apr 2019 13:36:07 +0200 Subject: [PATCH 394/401] chg: [pdf-enrich] updated --- misp_modules/modules/expansion/pdf-enrich.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/misp_modules/modules/expansion/pdf-enrich.py b/misp_modules/modules/expansion/pdf-enrich.py index 59d003e..ef85fde 100644 --- a/misp_modules/modules/expansion/pdf-enrich.py +++ b/misp_modules/modules/expansion/pdf-enrich.py @@ -1,10 +1,8 @@ import json import binascii import np -import pytesseract import pdftotext import io -import collections misperrors = {'error': 'Error'} mispattributes = {'input': ['attachment'], From ec766f571cf00429b43becec9d3a618c603281b7 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Fri, 26 Apr 2019 13:36:53 +0200 Subject: [PATCH 395/401] chg: [init] cleanup for pep --- 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 c4b2fdc..70aca68 100644 --- a/misp_modules/modules/expansion/__init__.py +++ b/misp_modules/modules/expansion/__init__.py @@ -9,5 +9,5 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c 'xforceexchange', 'sigma_syntax_validator', 'stix2_pattern_syntax_validator', 'sigma_queries', 'dbl_spamhaus', 'vulners', 'yara_query', 'macaddress_io', 'intel471', 'backscatter_io', 'btc_scam_check', 'hibp', 'greynoise', 'macvendors', - 'qrcode', 'ocr-enrich', 'pdf-enrich', 'docx-enrich', 'xlsx-enrich', 'pptx-enrich', + 'qrcode', 'ocr-enrich', 'pdf-enrich', 'docx-enrich', 'xlsx-enrich', 'pptx-enrich', 'ods-enrich', 'odt-enrich'] From 48c158271b5e3b0285ec8a4b915d1cf9d16784b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 26 Apr 2019 13:48:38 +0200 Subject: [PATCH 396/401] new: Devel mode. Fix #293 --- misp_modules/__init__.py | 52 ++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/misp_modules/__init__.py b/misp_modules/__init__.py index 68a2c69..440ad3f 100644 --- a/misp_modules/__init__.py +++ b/misp_modules/__init__.py @@ -210,37 +210,59 @@ class QueryModule(tornado.web.RequestHandler): self.finish() +def _launch_from_current_dir(): + log.info('Launch MISP modules server from current directory.') + os.chdir(os.path.dirname(__file__)) + modulesdir = 'modules' + helpersdir = 'helpers' + load_helpers(helpersdir=helpersdir) + return load_modules(modulesdir) + + def main(): global mhandlers global loaded_modules signal.signal(signal.SIGINT, handle_signal) signal.signal(signal.SIGTERM, handle_signal) - argParser = argparse.ArgumentParser(description='misp-modules server') + argParser = argparse.ArgumentParser(description='misp-modules server', formatter_class=argparse.RawTextHelpFormatter) argParser.add_argument('-t', default=False, action='store_true', help='Test mode') argParser.add_argument('-s', default=False, action='store_true', help='Run a system install (package installed via pip)') argParser.add_argument('-d', default=False, action='store_true', help='Enable debugging') argParser.add_argument('-p', default=6666, help='misp-modules TCP port (default 6666)') argParser.add_argument('-l', default='localhost', help='misp-modules listen address (default localhost)') argParser.add_argument('-m', default=[], action='append', help='Register a custom module') + argParser.add_argument('--devel', default=False, action='store_true', help='''Start in development mode, enable debug, start only the module(s) listed in -m.\nExample: -m misp_modules.modules.expansion.bgpranking''') args = argParser.parse_args() port = args.p listen = args.l - log = init_logger(level=args.d) - if args.s: - log.info('Launch MISP modules server from package.') - load_package_helpers() - mhandlers, loaded_modules = load_package_modules() + if args.devel: + log = init_logger(level=True) + log.info('Launch MISP modules server in developement mode. Enable debug, load a list of modules is -m is used.') + if args.m: + mhandlers = {} + modules = [] + for module in args.m: + splitted = module.split(".") + modulename = splitted[-1] + moduletype = splitted[2] + mhandlers[modulename] = importlib.import_module(module) + mhandlers['type:' + modulename] = moduletype + modules.append(modulename) + log.info('MISP modules {0} imported'.format(modulename)) + else: + mhandlers, loaded_modules = _launch_from_current_dir() else: - log.info('Launch MISP modules server from current directory.') - os.chdir(os.path.dirname(__file__)) - modulesdir = 'modules' - helpersdir = 'helpers' - load_helpers(helpersdir=helpersdir) - mhandlers, loaded_modules = load_modules(modulesdir) + log = init_logger(level=args.d) + if args.s: + log.info('Launch MISP modules server from package.') + load_package_helpers() + mhandlers, loaded_modules = load_package_modules() + else: + mhandlers, loaded_modules = _launch_from_current_dir() - for module in args.m: - mispmod = importlib.import_module(module) - mispmod.register(mhandlers, loaded_modules) + for module in args.m: + mispmod = importlib.import_module(module) + mispmod.register(mhandlers, loaded_modules) service = [(r'/modules', ListModules), (r'/query', QueryModule)] From 929dbd2463f159f23de7df4b0089409af5670b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 26 Apr 2019 13:49:16 +0200 Subject: [PATCH 397/401] chg: Bump dependencies. --- Pipfile | 2 +- Pipfile.lock | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index 16ac02e..762e4c5 100644 --- a/Pipfile +++ b/Pipfile @@ -45,7 +45,7 @@ backscatter = "*" pyzbar = "*" opencv-python = "*" np = "*" -ODTReader = {editable = true,git = "https://github.com/cartertemm/ODTReader.git"} +ODTReader = {editable = true,git = "https://github.com/cartertemm/ODTReader.git/"} python-pptx = "*" python-docx = "*" ezodf = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 5d395d5..6ac6493 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7fee9399d8a7151a79b6f8bbce64564062fd562b0a091fd45a875884d3fb954e" + "sha256": "1d4c69ced012268e1ab20cee76f76652e1acd9f5133636551d5686f15748d3b4" }, "pipfile-spec": 6, "requires": { @@ -150,6 +150,13 @@ ], "version": "==0.9" }, + "ezodf": { + "hashes": [ + "sha256:000da534f689c6d55297a08f9e2ed7eada9810d194d31d164388162fb391122d" + ], + "index": "pypi", + "version": "==0.3.2" + }, "future": { "hashes": [ "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8" @@ -184,6 +191,38 @@ ], "version": "==3.0.1" }, + "lxml": { + "hashes": [ + "sha256:03984196d00670b2ab14ae0ea83d5cc0cfa4f5a42558afa9ab5fa745995328f5", + "sha256:0815b0c9f897468de6a386dc15917a0becf48cc92425613aa8bbfc7f0f82951f", + "sha256:175f3825f075cf02d15099eb52658457cf0ff103dcf11512b5d2583e1d40f58b", + "sha256:30e14c62d88d1e01a26936ecd1c6e784d4afc9aa002bba4321c5897937112616", + "sha256:3210da6f36cf4b835ff1be853962b22cc354d506f493b67a4303c88bbb40d57b", + "sha256:40f60819fbd5bad6e191ba1329bfafa09ab7f3f174b3d034d413ef5266963294", + "sha256:43b26a865a61549919f8a42e094dfdb62847113cf776d84bd6b60e4e3fc20ea3", + "sha256:4a03dd682f8e35a10234904e0b9508d705ff98cf962c5851ed052e9340df3d90", + "sha256:62f382cddf3d2e52cf266e161aa522d54fd624b8cc567bc18f573d9d50d40e8e", + "sha256:7b98f0325be8450da70aa4a796c4f06852949fe031878b4aa1d6c417a412f314", + "sha256:846a0739e595871041385d86d12af4b6999f921359b38affb99cdd6b54219a8f", + "sha256:a3080470559938a09a5d0ec558c005282e99ac77bf8211fb7b9a5c66390acd8d", + "sha256:ad841b78a476623955da270ab8d207c3c694aa5eba71f4792f65926dc46c6ee8", + "sha256:afdd75d9735e44c639ffd6258ce04a2de3b208f148072c02478162d0944d9da3", + "sha256:b4fbf9b552faff54742bcd0791ab1da5863363fb19047e68f6592be1ac2dab33", + "sha256:b90c4e32d6ec089d3fa3518436bdf5ce4d902a0787dbd9bb09f37afe8b994317", + "sha256:b91cfe4438c741aeff662d413fd2808ac901cc6229c838236840d11de4586d63", + "sha256:bdb0593a42070b0a5f138b79b872289ee73c8e25b3f0bea6564e795b55b6bcdd", + "sha256:c4e4bca2bb68ce22320297dfa1a7bf070a5b20bcbaec4ee023f83d2f6e76496f", + "sha256:cec4ab14af9eae8501be3266ff50c3c2aecc017ba1e86c160209bb4f0423df6a", + "sha256:e83b4b2bf029f5104bc1227dbb7bf5ace6fd8fabaebffcd4f8106fafc69fc45f", + "sha256:e995b3734a46d41ae60b6097f7c51ba9958648c6d1e0935b7e0ee446ee4abe22", + "sha256:f679d93dec7f7210575c85379a31322df4c46496f184ef650d3aba1484b38a2d", + "sha256:fd213bb5166e46974f113c8228daaef1732abc47cb561ce9c4c8eaed4bd3b09b", + "sha256:fdcb57b906dbc1f80666e6290e794ab8fb959a2e17aa5aee1758a85d1da4533f", + "sha256:ff424b01d090ffe1947ec7432b07f536912e0300458f9a7f48ea217dd8362b86" + ], + "index": "pypi", + "version": "==4.3.3" + }, "maclookup": { "hashes": [ "sha256:33bf8eaebe3b1e4ab4ae9277dd93c78024e0ebf6b3c42f76c37695bc26ce287a", @@ -273,6 +312,11 @@ "index": "pypi", "version": "==1.9.0.post1" }, + "odtreader": { + "editable": true, + "git": "https://github.com/cartertemm/ODTReader.git/", + "ref": "49d6938693f6faa3ff09998f86dba551ae3a996b" + }, "opencv-python": { "hashes": [ "sha256:1703a296a96d3d46615e5053f224867977accb4240bcaa0fcabcb0768bf5ac13", @@ -306,6 +350,40 @@ "index": "pypi", "version": "==4.1.0.25" }, + "pandas": { + "hashes": [ + "sha256:071e42b89b57baa17031af8c6b6bbd2e9a5c68c595bc6bf9adabd7a9ed125d3b", + "sha256:17450e25ae69e2e6b303817bdf26b2cd57f69595d8550a77c308be0cd0fd58fa", + "sha256:17916d818592c9ec891cbef2e90f98cc85e0f1e89ed0924c9b5220dc3209c846", + "sha256:2538f099ab0e9f9c9d09bbcd94b47fd889bad06dc7ae96b1ed583f1dc1a7a822", + "sha256:366f30710172cb45a6b4f43b66c220653b1ea50303fbbd94e50571637ffb9167", + "sha256:42e5ad741a0d09232efbc7fc648226ed93306551772fc8aecc6dce9f0e676794", + "sha256:4e718e7f395ba5bfe8b6f6aaf2ff1c65a09bb77a36af6394621434e7cc813204", + "sha256:4f919f409c433577a501e023943e582c57355d50a724c589e78bc1d551a535a2", + "sha256:4fe0d7e6438212e839fc5010c78b822664f1a824c0d263fd858f44131d9166e2", + "sha256:5149a6db3e74f23dc3f5a216c2c9ae2e12920aa2d4a5b77e44e5b804a5f93248", + "sha256:627594338d6dd995cfc0bacd8e654cd9e1252d2a7c959449228df6740d737eb8", + "sha256:83c702615052f2a0a7fb1dd289726e29ec87a27272d775cb77affe749cca28f8", + "sha256:8c872f7fdf3018b7891e1e3e86c55b190e6c5cee70cab771e8f246c855001296", + "sha256:90f116086063934afd51e61a802a943826d2aac572b2f7d55caaac51c13db5b5", + "sha256:a3352bacac12e1fc646213b998bce586f965c9d431773d9e91db27c7c48a1f7d", + "sha256:bcdd06007cca02d51350f96debe51331dec429ac8f93930a43eb8fb5639e3eb5", + "sha256:c1bd07ebc15285535f61ddd8c0c75d0d6293e80e1ee6d9a8d73f3f36954342d0", + "sha256:c9a4b7c55115eb278c19aa14b34fcf5920c8fe7797a09b7b053ddd6195ea89b3", + "sha256:cc8fc0c7a8d5951dc738f1c1447f71c43734244453616f32b8aa0ef6013a5dfb", + "sha256:d7b460bc316064540ce0c41c1438c416a40746fd8a4fb2999668bf18f3c4acf1" + ], + "index": "pypi", + "version": "==0.24.2" + }, + "pandas-ods-reader": { + "hashes": [ + "sha256:0f7d510639c8957a06aa1227b9f84d1be47a437dfd306464ce803b91cf5eeec4", + "sha256:d85ef58fc3aeac1616028d22954b6ef2e8983ab9bae015e1e90ce3979d138553" + ], + "index": "pypi", + "version": "==0.0.6" + }, "passivetotal": { "hashes": [ "sha256:d745a6519ec04e3a354682978ebf07778bf7602beac30307cbad075ff1a4418d" @@ -313,6 +391,13 @@ "index": "pypi", "version": "==1.0.30" }, + "pdftotext": { + "hashes": [ + "sha256:e3ad11efe0aa22cbfc46aa1296b2ea5a52ad208b778288311f2801adef178ccb" + ], + "index": "pypi", + "version": "==2.1.1" + }, "pillow": { "hashes": [ "sha256:15c056bfa284c30a7f265a41ac4cbbc93bdbfc0dfe0613b9cb8a8581b51a9e55", @@ -447,6 +532,27 @@ ], "version": "==2.8.0" }, + "python-docx": { + "hashes": [ + "sha256:bc76ecac6b2d00ce6442a69d03a6f35c71cd72293cd8405a7472dfe317920024" + ], + "index": "pypi", + "version": "==0.8.10" + }, + "python-pptx": { + "hashes": [ + "sha256:1f2d5d1d923d91f50a1f0ed794935e7d670993fdcb6c12c81cc83977c1f23e14" + ], + "index": "pypi", + "version": "==0.6.17" + }, + "pytz": { + "hashes": [ + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + ], + "version": "==2019.1" + }, "pyyaml": { "hashes": [ "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", @@ -642,6 +748,14 @@ "index": "pypi", "version": "==0.5.3" }, + "xlrd": { + "hashes": [ + "sha256:546eb36cee8db40c3eaa46c351e67ffee6eeb5fa2650b71bc4c758a29a1b29b2", + "sha256:e551fb498759fa3a5384a94ccd4c3c02eb7c00ea424426e212ac0c57be9dfbde" + ], + "index": "pypi", + "version": "==1.2.0" + }, "xlsxwriter": { "hashes": [ "sha256:2a40b427dac0f640031e5b33abe97e761de6e0f12d4d346e7b2e2b67cf6ee927", From 980760790f9aa596c87694015b9f90cef2303f15 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Fri, 26 Apr 2019 13:51:17 +0200 Subject: [PATCH 398/401] chg: [doc] new MISP expansion modules added for PDF, OCR, DOCX, XLSX, PPTX , ODS and ODT. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 64d0960..cce1923 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [Cuckoo submit](misp_modules/modules/expansion/cuckoo_submit.py) - A hover module to submit malware sample, url, attachment, domain to Cuckoo Sandbox. * [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. +* [docx-enrich](misp_modules/modules/expansion/docx-enrich.py) - an enrichment module to get text out of Word document into MISP (using free-text parser). * [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). * [Farsight DNSDB Passive DNS](misp_modules/modules/expansion/farsight_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information. @@ -41,10 +42,15 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [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 from [MAC address Vendor Lookup](https://macaddress.io). See [integration tutorial here](https://macaddress.io/integrations/MISP-module). * [macvendors](misp_modules/modules/expansion/macvendors.py) - a hover module to retrieve mac vendor information. +* [ocr-enrich](misp_modules/modules/expansion/ocr-enrich.py) - an enrichment module to get OCRized data from images into MISP. +* [ods-enrich](misp_modules/modules/expansion/ods-enrich.py) - an enrichment module to get text out of OpenOffice spreadsheet document into MISP (using free-text parser). +* [odt-enrich](misp_modules/modules/expansion/odt-enrich.py) - an enrichment module to get text out of OpenOffice document into MISP (using free-text parser). * [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. +* [pdf-enrich](misp_modules/modules/expansion/pdf-enrich.py) - an enrichment module to extract text from PDF into MISP (using free-text parser). +* [pptx-enrich](misp_modules/modules/expansion/pptx-enrich.py) - an enrichment module to get text out of PowerPoint document into MISP (using free-text parser). * [qrcode](misp_modules/modules/expansion/qrcode.py) - a module decode QR code, barcode and similar codes from an image and enrich with the decoded values. * [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. @@ -64,6 +70,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ * [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. +* [xlsx-enrich](misp_modules/modules/expansion/xlsx-enrich.py) - an enrichment module to get text out of an Excel document into MISP (using free-text parser). * [YARA query](misp_modules/modules/expansion/yara_query.py) - a module to create YARA rules from single hash attributes. * [YARA syntax validator](misp_modules/modules/expansion/yara_syntax_validator.py) - YARA syntax validator. From d77fdabeb2fab2e7cdf010d8023f1678a6dba3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 26 Apr 2019 13:59:36 +0200 Subject: [PATCH 399/401] fix: Re-enable python 3.6 support --- Pipfile | 1 + Pipfile.lock | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 762e4c5..36f059e 100644 --- a/Pipfile +++ b/Pipfile @@ -54,6 +54,7 @@ pandas_ods_reader = "*" pdftotext = "*" lxml = "*" xlrd = "*" +idna-ssl = {markers="python_version < '3.7'"} [requires] python_version = "3" diff --git a/Pipfile.lock b/Pipfile.lock index 6ac6493..fa07b4c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1d4c69ced012268e1ab20cee76f76652e1acd9f5133636551d5686f15748d3b4" + "sha256": "9aac0a9c45df16b9502c13f9468095cf5ffdb8bc407fe2b55faee3ff53d8eba3" }, "pipfile-spec": 6, "requires": { @@ -177,6 +177,14 @@ ], "version": "==2.8" }, + "idna-ssl": { + "hashes": [ + "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c" + ], + "index": "pypi", + "markers": "python_version < '3.7'", + "version": "==1.1.0" + }, "isodate": { "hashes": [ "sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8", From c9281e605da93f7ab9923b798e1e92e4b51fc26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?= Date: Fri, 26 Apr 2019 14:05:02 +0200 Subject: [PATCH 400/401] chg: Bump REQUIREMENTS --- REQUIREMENTS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/REQUIREMENTS b/REQUIREMENTS index 37c5a68..9447c47 100644 --- a/REQUIREMENTS +++ b/REQUIREMENTS @@ -5,6 +5,7 @@ -e git+https://github.com/MISP/PyIntel471.git@0df8d51f1c1425de66714b3a5a45edb69b8cc2fc#egg=pyintel471 -e git+https://github.com/MISP/PyMISP.git@582dda0ce2a8ca8e1dd2cf3842e0491caca51c62#egg=pymisp -e git+https://github.com/Rafiot/uwhoisd.git@411572840eba4c72dc321c549b36a54ed5cea9de#egg=uwhois&subdirectory=client +-e git+https://github.com/cartertemm/ODTReader.git/@49d6938693f6faa3ff09998f86dba551ae3a996b#egg=odtreader -e git+https://github.com/sebdraven/pydnstrails@48c1f740025c51289f43a24863d1845ff12fd21a#egg=pydnstrails -e git+https://github.com/sebdraven/pyonyphe@cbb0168d5cb28a9f71f7ab3773164a7039ccdb12#egg=pyonyphe aiohttp==3.4.4 @@ -23,18 +24,24 @@ dnspython==1.16.0 domaintools-api==0.3.3 enum-compat==0.0.2 ez-setup==0.9 +ezodf==0.3.2 future==0.17.1 httplib2==0.12.3 +idna-ssl==1.1.0 ; python_version < '3.7' idna==2.8 isodate==0.6.0 jsonschema==3.0.1 +lxml==4.3.3 maclookup==1.0.3 multidict==4.5.2 np==1.0.2 numpy==1.16.3 oauth2==1.9.0.post1 opencv-python==4.1.0.25 +pandas-ods-reader==0.0.6 +pandas==0.24.2 passivetotal==1.0.30 +pdftotext==2.1.1 pillow==6.0.0 psutil==5.6.2 pyeupi==1.0 @@ -45,6 +52,9 @@ pypssl==2.1 pyrsistent==0.15.1 pytesseract==0.2.6 python-dateutil==2.8.0 +python-docx==0.8.10 +python-pptx==0.6.17 +pytz==2019.1 pyyaml==5.1 pyzbar==0.1.8 rdflib==4.2.2 @@ -65,6 +75,7 @@ urlarchiver==0.2 urllib3==1.24.2 vulners==1.5.0 wand==0.5.3 +xlrd==1.2.0 xlsxwriter==1.1.7 yara-python==3.8.1 yarl==1.3.0 From c5cbfaedf63d02620f1c142576f035e64f3a1679 Mon Sep 17 00:00:00 2001 From: Alexandre Dulaunoy Date: Sat, 27 Apr 2019 09:08:33 +0200 Subject: [PATCH 401/401] chg: [doc] install of deps updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cce1923..6ec5bc6 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/ ## How to install and start MISP modules in a Python virtualenv? (recommended) ~~~~bash -sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr imagemagick virtualenv libopencv-dev zbar-tools +sudo apt-get install python3-dev python3-pip libpq5 libjpeg-dev tesseract-ocr libpoppler-cpp-dev imagemagick virtualenv libopencv-dev zbar-tools libzbar0 libzbar-dev libfuzzy-dev -y sudo -u www-data virtualenv -p python3 /var/www/MISP/venv cd /usr/local/src/ sudo git clone https://github.com/MISP/misp-modules.git