├── .gitchangelog.rc ├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── pytest.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── CHANGELOG.txt ├── LICENSE ├── README.md ├── docs ├── Makefile ├── PDF-export │ ├── ExportPDF.png │ ├── Internationalization_parameters.png │ ├── Internationalization_path.png │ ├── Parameters.png │ └── README.MD ├── source │ ├── README.md │ ├── conf.py │ ├── index.rst │ ├── modules.rst │ └── tools.rst └── tutorial │ ├── FullOverview.ipynb │ ├── Search-FullOverview.ipynb │ ├── a.7-rest-api-extensive-restsearch.ipynb │ ├── install_notebook.sh │ └── old │ ├── PyMISP_tutorial.ipynb │ ├── Search.ipynb │ └── Usage-NG.ipynb ├── examples ├── __init__.py ├── add_attributes_from_csv.py ├── add_email_object.py ├── add_fail2ban_object.py ├── add_feed.py ├── add_file_object.py ├── add_filetype_object_from_csv.py ├── add_generic_object.py ├── add_github_user.py ├── add_gitlab_user.py ├── add_named_attribute.py ├── add_organisations.py ├── add_ssh_authorized_keys.py ├── add_user.py ├── add_vehicle_object.py ├── addtag2.py ├── admin │ └── setup_sync.py ├── asciidoc_generator.py ├── cache_all.py ├── climate │ └── scrippsco2.py ├── copyTagsFromAttributesToEvent.py ├── copy_list.py ├── covid19 │ ├── import_csse_covid19_daily.py │ └── import_dxy_covid19_live.py ├── create_events.py ├── cytomic_orion.py ├── del.py ├── delete_user.py ├── edit_organisation.py ├── edit_user.py ├── events │ ├── README.md │ ├── create_dummy_event.py │ ├── create_massive_dummy_events.py │ ├── dummy │ └── tools.py ├── falsepositive_disabletoids.py ├── feed-generator-from-redis │ ├── MISPItemToRedis.py │ ├── ObjectConstructor │ │ └── CowrieMISPObject.py │ ├── README.md │ ├── fromredis.py │ ├── generator.py │ ├── install.sh │ ├── server.py │ └── settings.default.py ├── feed-generator │ ├── README.md │ ├── generate.py │ ├── output │ │ └── empty │ └── settings.default.py ├── fetch_events_feed.py ├── fetch_warninglist_hits.py ├── freetext.py ├── freetext.txt ├── generate_file_objects.py ├── generate_meta_feed.py ├── get.py ├── get_csv.py ├── get_network_activity.event_id ├── get_network_activity.py ├── graphdb │ └── make_neo4j.py ├── ioc_2_misp │ ├── README.md │ ├── ioc2misp.py │ ├── keys.py.sample │ └── taxonomy.csv ├── keys.py.sample ├── last.py ├── load_csv.py ├── lookup.py ├── misp2cef.py ├── misp2clamav.py ├── openioc_to_misp.py ├── profiles │ ├── __init__.py │ ├── daily_report.py │ └── weekly_report.py ├── proofpoint_tap.py ├── proofpoint_vap.py ├── search.py ├── search_attributes_yara.py ├── search_sighting.py ├── server_sync_check_conn.py ├── sharing_groups.py ├── show_sightings.py ├── sighting.json ├── situational_awareness │ ├── README.md │ ├── __init__.py │ ├── attribute_treemap.py │ ├── bokeh_tools.py │ ├── date_tools.py │ ├── pygal_tools.py │ ├── style.css │ ├── style2.css │ ├── tag_scatter.py │ ├── tag_search.py │ ├── tags_count.py │ ├── tags_to_graphs.py │ ├── test_attribute_treemap.html │ └── tools.py ├── stats_report.py ├── suricata_search │ └── README.md ├── sync_sighting.py ├── tags.py ├── test_sign.py ├── trustar.conf ├── trustar_misp.py ├── up.py ├── upload.py ├── user_sample.json ├── users_list.py ├── vmray_automation.py ├── vt_to_misp.py ├── warninglists.py ├── yara.py └── yara_dump.py ├── mypy.ini ├── poetry.lock ├── pymisp ├── __init__.py ├── abstract.py ├── api.py ├── data │ ├── describeTypes.json │ ├── schema-lax.json │ └── schema.json ├── exceptions.py ├── mispevent.py ├── py.typed └── tools │ ├── __init__.py │ ├── _psl_faup.py │ ├── abstractgenerator.py │ ├── asnobject.py │ ├── create_misp_object.py │ ├── csvloader.py │ ├── domainipobject.py │ ├── elfobject.py │ ├── emailobject.py │ ├── ext_lookups.py │ ├── fail2banobject.py │ ├── feed.py │ ├── fileobject.py │ ├── genericgenerator.py │ ├── geolocationobject.py │ ├── git_vuln_finder_object.py │ ├── load_warninglists.py │ ├── machoobject.py │ ├── microblogobject.py │ ├── neo4j.py │ ├── openioc.py │ ├── peobject.py │ ├── reportlab_generator.py │ ├── sbsignatureobject.py │ ├── sshauthkeyobject.py │ ├── update_objects.py │ ├── urlobject.py │ ├── vehicleobject.py │ └── vtreportobject.py ├── pyproject.toml └── tests ├── 57c4445b-c548-4654-af0b-4be3950d210f.json ├── __init__.py ├── csv_testfiles ├── invalid_fieldnames.csv └── valid_fieldnames.csv ├── email_testfiles ├── mail_1.eml.zip ├── mail_1.msg.zip ├── mail_1_bom.eml ├── mail_1_headers_only.eml ├── mail_2.eml ├── mail_3.eml ├── mail_3.msg ├── mail_4.msg ├── mail_5.msg ├── mail_multiple_to.eml └── source ├── git-vuln-finder-quagga.json ├── misp_event.json ├── mispevent_testfiles ├── attribute.json ├── attribute_del.json ├── def_param.json ├── event.json ├── event_obj_attr_tag.json ├── event_obj_def_param.json ├── event_obj_tag.json ├── event_tags.json ├── existing_event.json ├── existing_event_edited.json ├── galaxy.json ├── malware.json ├── malware_exist.json ├── misp_custom_obj.json ├── overwrite_file │ └── definition.json ├── proposals.json ├── shadow.json ├── sighting.json ├── simple.json └── test_object_template │ └── definition.json ├── new_misp_event.json ├── reportlab_testfiles ├── HTML_event.json ├── galaxy_1.json ├── image_event.json ├── japanese_test.json ├── japanese_test_heavy.json ├── long_event.json ├── mainly_objects_1.json ├── mainly_objects_2.json ├── sighting_1.json ├── sighting_2.json ├── to_delete1.json ├── to_delete2.json ├── to_delete3.json └── very_long_event.json ├── reportlab_testoutputs ├── to_delete1.json.pdf ├── to_delete2.json.pdf └── to_delete3.json.pdf ├── search_index_result.json ├── sharing_groups.json ├── stix1.xml-utf8 ├── stix2.json ├── test_analyst_data.py ├── test_emailobject.py ├── test_fileobject.py ├── test_mispevent.py ├── test_reportlab.py ├── testlive_comprehensive.py └── testlive_sync.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | # Check for updates to GitHub Actions every weekday 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '21 10 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v3 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v3 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | name: Python application 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] 17 | 18 | steps: 19 | 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: recursive 23 | 24 | - name: Set up Python ${{matrix.python-version}} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{matrix.python-version}} 28 | 29 | - name: Install python 3.13 specific dependencies 30 | if: ${{ matrix.python-version == '3.13' }} 31 | run: | 32 | sudo apt-get install -y build-essential python3-dev libfuzzy-dev 33 | 34 | - name: Install Python dependencies 35 | run: | 36 | python -m pip install --upgrade pip poetry 37 | poetry install -E fileobjects -E openioc -E virustotal -E docs -E pdfexport -E url -E email -E brotli -vvv 38 | 39 | - name: Test with nosetests 40 | run: | 41 | poetry run pytest --cov=pymisp tests/test_*.py 42 | poetry run mypy . 43 | 44 | - name: Test with nosetests with orjson 45 | run: | 46 | pip3 install orjson 47 | poetry run pytest --cov=pymisp tests/test_*.py 48 | 49 | - name: Upload coverage to Codecov 50 | uses: codecov/codecov-action@v5 51 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: 4 | - published 5 | 6 | name: release 7 | 8 | jobs: 9 | pypi-publish: 10 | name: Upload release to PyPI 11 | runs-on: ubuntu-latest 12 | environment: 13 | name: pypi 14 | url: https://pypi.org/p/pymisp 15 | permissions: 16 | id-token: write # IMPORTANT: this permission is mandatory for trusted publishing 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | submodules: 'recursive' 22 | - name: Install Poetry 23 | run: python -m pip install --upgrade pip poetry 24 | - name: Build artifacts 25 | run: poetry build 26 | - name: Publish package distributions to PyPI 27 | uses: pypa/gh-action-pypi-publish@release/v1 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pem 3 | *.pyc 4 | docs/build/ 5 | examples/keys.py 6 | examples/cudeso.py 7 | examples/feed-generator/output/*\.json 8 | examples/feed-generator/output/hashes\.csv 9 | examples/feed-generator/settings\.py 10 | examples/feed_generator/output/*\.json 11 | examples/feed_generator/output/hashes\.csv 12 | examples/feed_generator/settings\.py 13 | tests/reportlab_testoutputs/*\.pdf 14 | build/* 15 | dist/* 16 | pymisp.egg-info/* 17 | .coverage 18 | .idea 19 | 20 | tests/keys.py 21 | 22 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "pymisp/data/misp-objects"] 2 | path = pymisp/data/misp-objects 3 | url = https://github.com/MISP/misp-objects 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | exclude: "tests/data" 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v4.6.0 7 | hooks: 8 | - id: trailing-whitespace 9 | - id: end-of-file-fixer 10 | - id: check-yaml 11 | - id: check-added-large-files 12 | - repo: https://github.com/asottile/pyupgrade 13 | rev: v3.17.0 14 | hooks: 15 | - id: pyupgrade 16 | args: [--py38-plus] 17 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: "ubuntu-lts-latest" 4 | tools: 5 | python: "3" 6 | 7 | sphinx: 8 | configuration: docs/source/conf.py 9 | 10 | python: 11 | install: 12 | - method: pip 13 | path: . 14 | extra_requirements: 15 | - docs 16 | 17 | formats: all 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, 2014 Raphaël Vinot 2 | Copyright (c) 2013, 2014 Alexandre Dulaunoy 3 | Copyright (c) 2013, 2014 CIRCL - Computer Incident Response Center Luxembourg 4 | (c/o smile, security made in Lëtzebuerg, Groupement 5 | d'Intérêt Economique) 6 | Copyright (c) 2014 Koen Van Impe 7 | 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without modification, 11 | are permitted provided that the following conditions are met: 12 | 13 | 1. Redistributions of source code must retain the above copyright notice, 14 | this list of conditions and the following disclaimer. 15 | 2. Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 23 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 27 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 28 | OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /docs/PDF-export/ExportPDF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/docs/PDF-export/ExportPDF.png -------------------------------------------------------------------------------- /docs/PDF-export/Internationalization_parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/docs/PDF-export/Internationalization_parameters.png -------------------------------------------------------------------------------- /docs/PDF-export/Internationalization_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/docs/PDF-export/Internationalization_path.png -------------------------------------------------------------------------------- /docs/PDF-export/Parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/docs/PDF-export/Parameters.png -------------------------------------------------------------------------------- /docs/PDF-export/README.MD: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | A big overview of the exportpdf tool is available at : 4 | ![Classes Diagram](./ExportPDF.png) 5 | 6 | ## Parameters 7 | ### Dynamic links 8 | You can specify your MISP URL to allow the PDF generator to create links from the PDF to your MISP instance events. 9 | The parameter to specify is "base_url_for_dynamic_link". Leave blank if you don't want the PDF generator to generate URL. 10 | ### Name 11 | You can specify your organisation name, to be added to the PDF metadata. 12 | The parameter to specify is "name_for_metadata". Leave blank if you don't want the PDF generator to add this name to the metadata. 13 | ### Textual description 14 | You can specify if you want a textual description of the event to be prepend to the generated PDF. 15 | The parameter to specify is "textual_description". Leave blank if you don't want the PDF generator to prepend this text description. 16 | ### Galaxy description 17 | You can specify if you want related galaxies to be added to the PDF. 18 | The parameter to specify is "galaxy_description". Leave blank if you don't want the PDF generator to add galaxies description. 19 | ### Related events 20 | You can specify if you want related events to be added to the PDF. Be aware this option might leads to information leaks if you have confidential events in your MISP instance. 21 | The parameter to specify is "related_events". Leave blank if you don't want the PDF generator to add related events.. 22 | 23 | Example of parameters all activated. 24 | ![Parameters](./Parameters.png) 25 | 26 | 27 | ## International fonts 28 | If text of exported events does not show up in the final PDF, Fonts may be the source of the problem. 29 | For that, you can activate a international font, which handle CJK characters, named "Noto". 30 | 31 | Due to their size, fonts are not bundled with PyMISP. You can download them, by following next steps : 32 | 33 | Manual fonts install on a MISP instance, connected in SSH : 34 | > cd /usr/local/lib/python3.6/dist-packages/pymisp/tools/ 35 | > git clone https://github.com/MISP/pdf_fonts 36 | 37 | Then you can activate the option by filling the following parameter : 38 | ![Internanlization parameters](./Internationalization_parameters.png) 39 | Leave blank if you don't want to use internationalization fonts. 40 | 41 | If "Noto" is not fine for you (e.g. you want to use Arial Unicode[,](https://github.com/Vincent-CIRCL/Arial_Unicode) etc.) you can give as a parameter the font to use. Be sure the font is only contained in one TTF. 42 | 43 | Fill this parameter, with the font's TTF's absolute path, as follow : 44 | ![Internanlization path](./Internationalization_path.png) 45 | Note that if you give a custom fonts, bold/italic/special styles won't be used in the final PDF. 46 | Leave blank if you don't want to use your custom font. 47 | -------------------------------------------------------------------------------- /docs/source/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. PyMISP documentation master file, created by 2 | sphinx-quickstart on Fri Aug 26 11:39:17 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to PyMISP's documentation! 7 | ================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | README 15 | modules 16 | tools 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | -------------------------------------------------------------------------------- /docs/source/tools.rst: -------------------------------------------------------------------------------- 1 | pymisp - Tools 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | .. automodule:: pymisp.tools 8 | :members: 9 | 10 | File Object 11 | ----------- 12 | 13 | .. autoclass:: FileObject 14 | :members: 15 | :inherited-members: 16 | 17 | ELF Object 18 | ---------- 19 | 20 | .. autoclass:: ELFObject 21 | :members: 22 | :inherited-members: 23 | 24 | .. autoclass:: ELFSectionObject 25 | :members: 26 | :inherited-members: 27 | 28 | PE Object 29 | --------- 30 | 31 | .. autoclass:: PEObject 32 | :members: 33 | :inherited-members: 34 | 35 | .. autoclass:: PESectionObject 36 | :members: 37 | :inherited-members: 38 | 39 | Mach-O Object 40 | ------------- 41 | 42 | .. autoclass:: MachOObject 43 | :members: 44 | :inherited-members: 45 | 46 | .. autoclass:: MachOSectionObject 47 | :members: 48 | :inherited-members: 49 | 50 | VT Report Object 51 | ---------------- 52 | 53 | .. autoclass:: VTReportObject 54 | :members: 55 | :inherited-members: 56 | 57 | STIX 58 | ---- 59 | 60 | .. automodule:: pymisp.tools.stix 61 | :members: 62 | 63 | OpenIOC 64 | -------- 65 | 66 | .. automethod:: pymisp.tools.load_openioc 67 | 68 | .. automethod:: pymisp.tools.load_openioc_file 69 | 70 | -------------------------------------------------------------------------------- /docs/tutorial/install_notebook.sh: -------------------------------------------------------------------------------- 1 | pip3 install --upgrade pip 2 | pip3 install jupyter 3 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/examples/__init__.py -------------------------------------------------------------------------------- /examples/add_attributes_from_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import csv 5 | from pymisp import PyMISP 6 | from pymisp import PyMISP, MISPAttribute 7 | from keys import misp_url, misp_key, misp_verifycert 8 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 9 | import argparse 10 | import urllib3 11 | import requests 12 | requests.packages.urllib3.disable_warnings() 13 | 14 | 15 | """ 16 | 17 | Sample usage: 18 | 19 | python3 add_filetype_object_from_csv.py -e -f .csv 20 | 21 | 22 | Attribute CSV file (aach line is an entry): 23 | 24 | value;category;type;comment;to_ids;first_seen;last_seen;tag1;tag2 25 | test.pdf;Payload delivery;filename;Email attachment;0;1970-01-01;1970-01-01;tlp:green;ransomware 26 | 127.0.0.1;Network activity;ip-dst;C2 server;1;;;tlp:white; 27 | 28 | value = IOC's value 29 | category = its MISP category (https://www.circl.lu/doc/misp/categories-and-types/) 30 | type = its MISP type (https://www.circl.lu/doc/misp/categories-and-types/) 31 | comment = IOC's description 32 | to_ids = Boolean expected (0 = IDS flag not checked // 1 = IDS flag checked) 33 | first_seen = First seen date, if any (left empty if not) 34 | last_seen = Last seen date, if any (left empty if not) 35 | tag = IOC tag, if any 36 | 37 | """ 38 | 39 | if __name__ == '__main__': 40 | parser = argparse.ArgumentParser(description='Add attributes to a MISP event from a semi-colon formated csv file') 41 | parser.add_argument("-e", "--event_uuid", required=True, help="Event UUID to update") 42 | parser.add_argument("-f", "--attr_file", required=True, help="Attribute CSV file path") 43 | args = parser.parse_args() 44 | 45 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert) 46 | 47 | f = open(args.attr_file, newline='') 48 | csv_reader = csv.reader(f, delimiter=";") 49 | 50 | for line in csv_reader: 51 | value = line[0] 52 | category = line[1] 53 | type = line[2] 54 | comment = line[3] 55 | ids = line[4] 56 | fseen = line[5] 57 | lseen = line[6] 58 | tags = line[7:] 59 | 60 | misp_attribute = MISPAttribute() 61 | misp_attribute.value = str(value) 62 | misp_attribute.category = str(category) 63 | misp_attribute.type = str(type) 64 | misp_attribute.comment = str(comment) 65 | misp_attribute.to_ids = str(ids) 66 | if fseen != '': 67 | misp_attribute.first_seen = str(fseen) 68 | if lseen != '': 69 | misp_attribute.last_seen = str(lseen) 70 | for x in tags: 71 | misp_attribute.add_tag(x) 72 | r = pymisp.add_attribute(args.event_uuid, misp_attribute) 73 | print(line) 74 | print("\nAttributes successfully saved :)") 75 | -------------------------------------------------------------------------------- /examples/add_email_object.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pymisp import PyMISP 4 | from pymisp.tools import EMailObject 5 | import traceback 6 | from keys import misp_url, misp_key, misp_verifycert # type: ignore 7 | import glob 8 | import argparse 9 | 10 | 11 | if __name__ == '__main__': 12 | parser = argparse.ArgumentParser(description='Extract indicators out of binaries and add MISP objects to a MISP instance.') 13 | parser.add_argument("-e", "--event", required=True, help="Event ID to update.") 14 | parser.add_argument("-p", "--path", required=True, help="Path to process (expanded using glob).") 15 | args = parser.parse_args() 16 | 17 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert, debug=True) 18 | 19 | for f in glob.glob(args.path): 20 | try: 21 | eo = EMailObject(f) 22 | except Exception: 23 | traceback.print_exc() 24 | continue 25 | 26 | if eo: 27 | response = pymisp.add_object(args.event, eo, pythonify=True) 28 | for ref in eo.ObjectReference: 29 | r = pymisp.add_object_reference(ref) 30 | -------------------------------------------------------------------------------- /examples/add_feed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP, MISPFeed 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | if __name__ == '__main__': 9 | parser = argparse.ArgumentParser(description='Add a feed') 10 | parser.add_argument("-f", "--format", required=True, choices=['misp', 'csv', 'freetext'], help="Feed source format") 11 | parser.add_argument("-u", "--url", required=True, help="URL, or local path") 12 | parser.add_argument("-n", "--name", required=True, help="Name of the feed") 13 | parser.add_argument("-i", "--input", required=True, choices=['local', 'network'], help="URL, or local path") 14 | parser.add_argument("-p", "--provider", required=True, help="Provider name") 15 | args = parser.parse_args() 16 | 17 | pm = PyMISP(misp_url, misp_key, misp_verifycert, debug=True) 18 | feed = MISPFeed() 19 | feed.format = args.format 20 | feed.url = args.url 21 | feed.name = args.name 22 | feed.input = args.input 23 | feed.provider = args.provider 24 | response = pm.add_feed(feed, pythonify=True) 25 | print(response.to_json()) 26 | -------------------------------------------------------------------------------- /examples/add_file_object.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from pymisp.tools import make_binary_objects 6 | import traceback 7 | from keys import misp_url, misp_key, misp_verifycert 8 | import glob 9 | import argparse 10 | 11 | if __name__ == '__main__': 12 | parser = argparse.ArgumentParser(description='Extract indicators out of binaries and add MISP objects to a MISP instance.') 13 | parser.add_argument("-e", "--event", required=True, help="Event ID to update.") 14 | parser.add_argument("-p", "--path", required=True, help="Path to process (expanded using glob).") 15 | args = parser.parse_args() 16 | 17 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert) 18 | 19 | for f in glob.glob(args.path): 20 | try: 21 | fo, peo, seos = make_binary_objects(f) 22 | except Exception: 23 | traceback.print_exc() 24 | continue 25 | 26 | if seos: 27 | for s in seos: 28 | r = pymisp.add_object(args.event, s) 29 | 30 | if peo: 31 | if hasattr(peo, 'certificates') and hasattr(peo, 'signers'): 32 | # special authenticode case for PE objects 33 | for c in peo.certificates: 34 | pymisp.add_object(args.event, c, pythonify=True) 35 | for s in peo.signers: 36 | pymisp.add_object(args.event, s, pythonify=True) 37 | del peo.certificates 38 | del peo.signers 39 | del peo.sections 40 | r = pymisp.add_object(args.event, peo, pythonify=True) 41 | for ref in peo.ObjectReference: 42 | r = pymisp.add_object_reference(ref) 43 | 44 | if fo: 45 | response = pymisp.add_object(args.event, fo, pythonify=True) 46 | for ref in fo.ObjectReference: 47 | r = pymisp.add_object_reference(ref) 48 | -------------------------------------------------------------------------------- /examples/add_filetype_object_from_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import csv 5 | from pymisp import PyMISP, MISPObject 6 | from keys import misp_url, misp_key, misp_verifycert 7 | import argparse 8 | 9 | 10 | """ 11 | 12 | Sample usage: 13 | 14 | python3 ./add_filetype_object_from_csv.py -e 77bcc9f4-21a8-4252-9353-f4615d6121e3 -f ./attributes.csv 15 | 16 | 17 | Attribute csv file (2 lines. Each line will be a file MISP Object): 18 | 19 | test.pdf;6ff19f8b680df260883d61d7c00db14a8bc57aa0;ea307d60ad0bd1df83ab5119df0bf638;b6c9903c9c38400345ad21faa2df50211d8878c96079c43ae64f35b17c9f74a1 20 | test2.xml;0dcef3d68f43e2badb0bfe3d47fd19633264cd1d;15f453625882f6123e239c9ce2b0fe24;b064514fcc52a769e064c4d61ce0c554fbc81e446af31dddac810879a5ca5b17 21 | 22 | """ 23 | 24 | 25 | if __name__ == '__main__': 26 | parser = argparse.ArgumentParser(description='Create a file type MISP Object starting from attributes in a csv file') 27 | parser.add_argument("-e", "--event_uuid", required=True, help="Event UUID to update") 28 | parser.add_argument("-f", "--attr_file", required=True, help="Attribute CSV file path") 29 | args = parser.parse_args() 30 | 31 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert) 32 | 33 | f = open(args.attr_file, newline='') 34 | csv_reader = csv.reader(f, delimiter=";") 35 | 36 | for line in csv_reader: 37 | filename = line[0] 38 | sha1 = line[1] 39 | md5 = line[2] 40 | sha256 = line[3] 41 | 42 | misp_object = MISPObject(name='file', filename=filename) 43 | obj1 = misp_object.add_attribute("filename", value = filename) 44 | obj1.add_tag('tlp:green') 45 | obj2 = misp_object.add_attribute("sha1", value = sha1) 46 | obj2.add_tag('tlp:amber') 47 | obj3 = misp_object.add_attribute("md5", value = md5) 48 | obj3.add_tag('tlp:amber') 49 | obj4 = misp_object.add_attribute("sha256", value = sha256) 50 | obj4.add_tag('tlp:amber') 51 | r = pymisp.add_object(args.event_uuid, misp_object) 52 | print(line) 53 | print("\nObjects created :)") 54 | -------------------------------------------------------------------------------- /examples/add_generic_object.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | from pymisp import PyMISP 6 | from pymisp.tools import GenericObjectGenerator 7 | from keys import misp_url, misp_key, misp_verifycert 8 | import argparse 9 | 10 | """ 11 | Sample usage: 12 | ./add_generic_object.py -e 5065 -t email -l '[{"to": "undisclosed@ppp.com"}, {"to": "second.to@mail.com"}]' 13 | """ 14 | 15 | if __name__ == '__main__': 16 | parser = argparse.ArgumentParser(description='Create a MISP Object selectable by type starting from a dictionary') 17 | parser.add_argument("-e", "--event", required=True, help="Event ID to update") 18 | parser.add_argument("-t", "--type", required=True, help="Type of the generic object") 19 | parser.add_argument("-l", "--attr_list", required=True, help="List of attributes") 20 | args = parser.parse_args() 21 | 22 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert) 23 | 24 | misp_object = GenericObjectGenerator(args.type.replace("|", "-")) 25 | misp_object.generate_attributes(json.loads(args.attr_list)) 26 | r = pymisp.add_object(args.event, misp_object) 27 | -------------------------------------------------------------------------------- /examples/add_github_user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from pymisp import MISPObject 6 | from pymisp.tools import update_objects 7 | from keys import misp_url, misp_key, misp_verifycert 8 | import argparse 9 | import requests 10 | import sys 11 | 12 | 13 | """ 14 | 15 | usage: add_github_user.py [-h] -e EVENT [-f] -u USERNAME 16 | 17 | Fetch GitHub user details and add it in object in MISP 18 | 19 | optional arguments: 20 | -h, --help show this help message and exit 21 | -e EVENT, --event EVENT 22 | Event ID to update 23 | -f, --force-template-update 24 | -u USERNAME, --username USERNAME 25 | GitHub username to add 26 | """ 27 | 28 | if __name__ == '__main__': 29 | parser = argparse.ArgumentParser(description='Fetch GitHub user details and add it in object in MISP') 30 | parser.add_argument("-e", "--event", required=True, help="Event ID to update") 31 | parser.add_argument("-f", "--force-template-update", required=False, action="store_true") 32 | parser.add_argument("-u", "--username", required=True, help="GitHub username to add") 33 | args = parser.parse_args() 34 | 35 | r = requests.get("https://api.github.com/users/{}".format(args.username)) 36 | if r.status_code != 200: 37 | sys.exit("HTTP return is {} and not 200 as expected".format(r.status_code)) 38 | if args.force_template_update: 39 | print("Updating MISP Object templates...") 40 | update_objects() 41 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert) 42 | 43 | misp_object = MISPObject(name="github-user") 44 | github_user = r.json() 45 | rfollowers = requests.get(github_user['followers_url']) 46 | followers = rfollowers.json() 47 | rfollowing = requests.get("https://api.github.com/users/{}/following".format(args.username)) 48 | followings = rfollowing.json() 49 | rkeys = requests.get("https://api.github.com/users/{}/keys".format(args.username)) 50 | keys = rkeys.json() 51 | misp_object.add_attributes("follower", *[follower['login'] for follower in followers]) 52 | misp_object.add_attributes("following", *[following['login'] for following in followings]) 53 | misp_object.add_attributes("ssh-public-key", *[sshkey['key'] for sshkey in keys]) 54 | misp_object.add_attribute('bio', github_user['bio']) 55 | misp_object.add_attribute('link', github_user['html_url']) 56 | misp_object.add_attribute('user-fullname', github_user['name']) 57 | misp_object.add_attribute('username', github_user['login']) 58 | misp_object.add_attribute('twitter_username', github_user['twitter_username']) 59 | misp_object.add_attribute('location', github_user['location']) 60 | misp_object.add_attribute('company', github_user['company']) 61 | misp_object.add_attribute('public_gists', github_user['public_gists']) 62 | misp_object.add_attribute('public_repos', github_user['public_repos']) 63 | misp_object.add_attribute('blog', github_user['blog']) 64 | misp_object.add_attribute('node_id', github_user['node_id']) 65 | retcode = pymisp.add_object(args.event, misp_object) 66 | -------------------------------------------------------------------------------- /examples/add_gitlab_user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from pymisp import MISPObject 6 | from pymisp.tools import update_objects 7 | from keys import misp_url, misp_key, misp_verifycert 8 | import argparse 9 | import requests 10 | import sys 11 | 12 | """ 13 | usage: add_gitlab_user.py [-h] -e EVENT [-f] -u USERNAME [-l LINK] 14 | 15 | Fetch GitLab user details and add it in object in MISP 16 | 17 | optional arguments: 18 | -h, --help show this help message and exit 19 | -e EVENT, --event EVENT 20 | Event ID to update 21 | -f, --force-template-update 22 | -u USERNAME, --username USERNAME 23 | GitLab username to add 24 | -l LINK, --link LINK Url to access the GitLab instance, Default is 25 | www.gitlab.com. 26 | """ 27 | 28 | default_url = "http://www.gitlab.com/" 29 | 30 | parser = argparse.ArgumentParser(description='Fetch GitLab user details and add it in object in MISP') 31 | parser.add_argument("-e", "--event", required=True, help="Event ID to update") 32 | parser.add_argument("-f", "--force-template-update", required=False, action="store_true") 33 | parser.add_argument("-u", "--username", required=True, help="GitLab username to add") 34 | parser.add_argument("-l", "--link", required=False, help="Url to access the GitLab instance, Default is www.gitlab.com.", default=default_url) 35 | args = parser.parse_args() 36 | 37 | 38 | r = requests.get("{}api/v4/users?username={}".format(args.link, args.username)) 39 | if r.status_code != 200: 40 | sys.exit("HTTP return is {} and not 200 as expected".format(r.status_code)) 41 | if args.force_template_update: 42 | print("Updating MISP Object templates...") 43 | update_objects() 44 | 45 | gitlab_user = r.json()[0] 46 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert) 47 | print(gitlab_user) 48 | 49 | misp_object = MISPObject(name="gitlab-user") 50 | misp_object.add_attribute('username', gitlab_user['username']) 51 | misp_object.add_attribute('id', gitlab_user['id']) 52 | misp_object.add_attribute('name', gitlab_user['name']) 53 | misp_object.add_attribute('state', gitlab_user['state']) 54 | misp_object.add_attribute('avatar_url', gitlab_user['avatar_url']) 55 | misp_object.add_attribute('web_url', gitlab_user['web_url']) 56 | retcode = pymisp.add_object(args.event, misp_object) 57 | -------------------------------------------------------------------------------- /examples/add_named_attribute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | # For python2 & 3 compat, a bit dirty, but it seems to be the least bad one 9 | try: 10 | input = raw_input 11 | except NameError: 12 | pass 13 | 14 | 15 | if __name__ == '__main__': 16 | parser = argparse.ArgumentParser(description='Add an attribute to an event') 17 | parser.add_argument("-e", "--event", help="The id, uuid or json of the event to update.") 18 | parser.add_argument("-t", "--type", help="The type of the added attribute") 19 | parser.add_argument("-v", "--value", help="The value of the attribute") 20 | args = parser.parse_args() 21 | 22 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 23 | 24 | event = misp.add_attribute(args.event, {'type': args.type, 'value': args.value}, pythonify=True) 25 | print(event) 26 | -------------------------------------------------------------------------------- /examples/add_organisations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP, MISPOrganisation, MISPSharingGroup 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | import csv 8 | 9 | 10 | # Suppress those "Unverified HTTPS request is being made" 11 | import urllib3 12 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 13 | 14 | 15 | if __name__ == '__main__': 16 | parser = argparse.ArgumentParser(description='Add organizations from a CSV file') 17 | parser.add_argument("-c", "--csv-import", required=True, help="The CSV file containing the organizations. Format 'orgname,nationality,sector,type,contacts,uuid,local,sharingroup_uuid'") 18 | args = parser.parse_args() 19 | 20 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 21 | 22 | # CSV format 23 | # orgname,nationality,sector,type,contacts,uuid,local,sharingroup 24 | with open(args.csv_import) as csv_file: 25 | count_orgs = 0 26 | csv_reader = csv.reader(csv_file, delimiter=',') 27 | for row in csv_reader: 28 | 29 | org = MISPOrganisation() 30 | org.name = row[0] 31 | print("Process {}".format(org.name)) 32 | org.nationality = row[1] 33 | org.sector = row[2] 34 | org.type = row[3] 35 | org.contacts = row[4] 36 | org.uuid = row[5] 37 | org.local = row[6] 38 | 39 | add_org = misp.add_organisation(org, pythonify=True) 40 | 41 | if 'errors' in add_org: 42 | print(add_org['errors']) 43 | else: 44 | count_orgs = count_orgs + 1 45 | org_uuid = add_org.uuid 46 | 47 | if org_uuid: 48 | sharinggroup = MISPSharingGroup() 49 | sharinggroup_uuid = row[7] 50 | 51 | if sharinggroup_uuid: 52 | sharinggroup.uuid = sharinggroup_uuid 53 | add_sharing = misp.add_org_to_sharing_group(sharinggroup, org) 54 | else: 55 | print("Organisation {} not added to sharing group, missing sharing group uuid".format(org.name)) 56 | 57 | print("Import finished, {} organisations added".format(count_orgs)) 58 | -------------------------------------------------------------------------------- /examples/add_ssh_authorized_keys.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from pymisp.tools import SSHAuthorizedKeysObject 6 | import traceback 7 | from keys import misp_url, misp_key, misp_verifycert 8 | import glob 9 | import argparse 10 | 11 | 12 | if __name__ == '__main__': 13 | parser = argparse.ArgumentParser(description='Extract indicators out of authorized_keys file.') 14 | parser.add_argument("-e", "--event", required=True, help="Event ID to update.") 15 | parser.add_argument("-p", "--path", required=True, help="Path to process (expanded using glob).") 16 | args = parser.parse_args() 17 | 18 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert, debug=True) 19 | 20 | for f in glob.glob(args.path): 21 | try: 22 | auth_keys = SSHAuthorizedKeysObject(f) 23 | except Exception: 24 | traceback.print_exc() 25 | continue 26 | 27 | response = pymisp.add_object(args.event, auth_keys, pythonify=True) 28 | for ref in auth_keys.ObjectReference: 29 | r = pymisp.add_object_reference(ref) 30 | -------------------------------------------------------------------------------- /examples/add_user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP, MISPUser 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | if __name__ == '__main__': 9 | parser = argparse.ArgumentParser(description='Add a new user by setting the mandory fields.') 10 | parser.add_argument("-e", "--email", required=True, help="Email linked to the account.") 11 | parser.add_argument("-o", "--org_id", required=True, help="Organisation linked to the user.") 12 | parser.add_argument("-r", "--role_id", required=True, help="Role linked to the user.") 13 | args = parser.parse_args() 14 | 15 | misp = PyMISP(misp_url, misp_key, misp_verifycert, 'json') 16 | 17 | user = MISPUser() 18 | user.email = args.email 19 | user.org_id = args.org_id 20 | user.role_id = args.role_id 21 | 22 | print(misp.add_user(user, pythonify=True)) 23 | -------------------------------------------------------------------------------- /examples/add_vehicle_object.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp.tools import VehicleObject 5 | import argparse 6 | 7 | 8 | if __name__ == '__main__': 9 | parser = argparse.ArgumentParser(description='Get information for a VehicleObject and add MISP objects to a MISP instance.') 10 | parser.add_argument("-u", "--username", required=True, help="Account username.") 11 | parser.add_argument("-c", "--country", required=True, help="Country.") 12 | parser.add_argument("-r", "--registration", required=True, help="Registration ID.") 13 | parser.add_argument("-d", "--dump", action='store_true', help="(Debug) Dump the object in the terminal.") 14 | args = parser.parse_args() 15 | 16 | if args.dump: 17 | vehicle = VehicleObject(country=args.country, registration=args.registration, username=args.username) 18 | print(vehicle.report) 19 | print(vehicle.to_json()) 20 | else: 21 | # not Implemented yet. 22 | pass 23 | -------------------------------------------------------------------------------- /examples/addtag2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pymisp import PyMISP 4 | from keys import misp_url, misp_key, misp_verifycert 5 | import argparse 6 | 7 | 8 | def init(url, key): 9 | return PyMISP(url, key, misp_verifycert, 'json') 10 | 11 | 12 | if __name__ == '__main__': 13 | parser = argparse.ArgumentParser(description='Tag something.') 14 | parser.add_argument("-u", "--uuid", help="UUID to tag.") 15 | parser.add_argument("-e", "--event", help="Event ID to tag.") 16 | parser.add_argument("-a", "--attribute", help="Attribute ID to tag") 17 | parser.add_argument("-t", "--tag", required=True, help="Tag ID.") 18 | args = parser.parse_args() 19 | 20 | if not args.event and not args.uuid and not args.attribute: 21 | print("Please provide at least one of the following : uuid, eventID or attribute ID, see --help") 22 | exit() 23 | 24 | misp = init(misp_url, misp_key) 25 | 26 | if args.event and not args.attribute: 27 | result = misp.search(eventid=args.event) 28 | for event in result: 29 | uuid = event['Event']['uuid'] 30 | 31 | if args.attribute: 32 | if not args.event: 33 | print("Please provide event ID also") 34 | exit() 35 | result = misp.search(eventid=args.event) 36 | for event in result: 37 | for attribute in event['Event']['Attribute']: 38 | if attribute["id"] == args.attribute: 39 | uuid = attribute["uuid"] 40 | 41 | if args.uuid: 42 | uuid = args.uuid 43 | 44 | print("UUID tagged: %s" % uuid) 45 | misp.tag(uuid, args.tag) 46 | -------------------------------------------------------------------------------- /examples/admin/setup_sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | import sys 6 | import json 7 | 8 | 9 | # NOTE: the user of the API key *need to be a sync user* 10 | remote_url = 'https://misp.remote' 11 | remote_api_key = 'REMOTE KEY FOR SYNC USER' 12 | remote_verify = True 13 | 14 | # NOTE: the user of the API key *need to be an admin* 15 | own_url = 'https://misp.own' 16 | own_api_key = 'OWN KEY FOR ADMIN USER' 17 | own_verify = True 18 | 19 | 20 | remote_misp = PyMISP(url=remote_url, key=remote_api_key, ssl=remote_verify) 21 | sync_config = remote_misp.get_sync_config() 22 | 23 | if 'errors' in sync_config: 24 | print('Sumething went wrong:') 25 | print(json.dumps(sync_config, indent=2)) 26 | sys.exit(1) 27 | else: 28 | print('Sucessfully got a sync config:') 29 | print(json.dumps(sync_config, indent=2)) 30 | 31 | own_misp = PyMISP(url=own_url, key=own_api_key, ssl=own_verify) 32 | response = own_misp.import_server(sync_config) 33 | 34 | if 'errors' in response: 35 | print('Sumething went wrong:') 36 | print(json.dumps(response, indent=2)) 37 | sys.exit(1) 38 | else: 39 | print('Sucessfully added the sync config:') 40 | print(json.dumps(response, indent=2)) 41 | -------------------------------------------------------------------------------- /examples/cache_all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from keys import misp_url, misp_key, misp_verifycert 5 | from pymisp import PyMISP 6 | 7 | 8 | if __name__ == '__main__': 9 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 10 | misp.cache_all_feeds() 11 | -------------------------------------------------------------------------------- /examples/copyTagsFromAttributesToEvent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | import os 8 | 9 | SILENT = False 10 | 11 | 12 | def getTagToApplyToEvent(event): 13 | tags_to_apply = set() 14 | 15 | event_tags = { tag.name for tag in event.tags } 16 | for galaxy in event.galaxies: 17 | for cluster in galaxy.clusters: 18 | event_tags.add(cluster.tag_name) 19 | 20 | for attribute in event.attributes: 21 | for attribute_tag in attribute.tags: 22 | if attribute_tag.name not in event_tags: 23 | tags_to_apply.add(attribute_tag.name) 24 | 25 | return tags_to_apply 26 | 27 | 28 | def TagEvent(event, tags_to_apply): 29 | for tag in tags_to_apply: 30 | event.add_tag(tag) 31 | return event 32 | 33 | 34 | def condPrint(text): 35 | if not SILENT: 36 | print(text) 37 | 38 | 39 | if __name__ == '__main__': 40 | parser = argparse.ArgumentParser(description='Get an event from a MISP instance.') 41 | parser.add_argument("-e", "--event", required=True, help="Event ID to get.") 42 | parser.add_argument("-y", "--yes", required=False, default=False, action='store_true', help="Automatically accept prompt.") 43 | parser.add_argument("-s", "--silent", required=False, default=False, action='store_true', help="No output to stdin.") 44 | 45 | args = parser.parse_args() 46 | SILENT = args.silent 47 | 48 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 49 | 50 | event = misp.get_event(args.event, pythonify=True) 51 | tags_to_apply = getTagToApplyToEvent(event) 52 | condPrint('Tag to apply at event level:') 53 | for tag in tags_to_apply: 54 | condPrint(f'- {tag}') 55 | 56 | confirmed = False 57 | if args.yes: 58 | confirmed = True 59 | else: 60 | confirm = input('Confirm [Y/n]: ') 61 | confirmed = len(confirm) == 0 or confirm == 'Y' or confirm == 'y' 62 | if confirmed: 63 | event = TagEvent(event, tags_to_apply) 64 | condPrint(f'Updating event {args.event}') 65 | misp.update_event(event) 66 | condPrint(f'Event {args.event} tagged with {len(tags_to_apply)} tags') 67 | else: 68 | condPrint('Operation cancelled') 69 | -------------------------------------------------------------------------------- /examples/copy_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | 6 | from pymisp import PyMISP 7 | 8 | from keys import cert, priv 9 | 10 | url_cert = 'https://misp.circl.lu' 11 | url_priv = 'https://misppriv.circl.lu' 12 | cert_cert = 'misp.circl.lu.crt' 13 | cert_priv = 'misppriv.circl.lu.crt' 14 | source = None 15 | destination = None 16 | 17 | 18 | def init(cert_to_priv=True): 19 | global source 20 | global destination 21 | print(cert_to_priv) 22 | if cert_to_priv: 23 | source = PyMISP(url_cert, cert, cert_cert, 'xml') 24 | destination = PyMISP(url_priv, priv, cert_priv, 'xml') 25 | else: 26 | source = PyMISP(url_priv, priv, cert_priv, 'xml') 27 | destination = PyMISP(url_cert, cert, cert_cert, 'xml') 28 | 29 | 30 | def copy_event(event_id): 31 | e = source.get_event(event_id) 32 | return destination.add_event(e) 33 | 34 | 35 | def update_event(event_id, event_to_update): 36 | e = source.get_event(event_id) 37 | return destination.update_event(event_to_update, e) 38 | 39 | 40 | def list_copy(filename): 41 | with open(filename, 'r') as f: 42 | for l in f: 43 | copy(l) 44 | 45 | 46 | def loop_copy(): 47 | while True: 48 | line = sys.stdin.readline() 49 | copy(line) 50 | 51 | 52 | def copy(eventid): 53 | eventid = eventid.strip() 54 | if len(eventid) == 0 or not eventid.isdigit(): 55 | print('empty line or NaN.') 56 | return 57 | eventid = int(eventid) 58 | print(eventid, 'copying...') 59 | r = copy_event(eventid) 60 | if r.status_code >= 400: 61 | loc = r.headers['location'] 62 | if loc is not None: 63 | event_to_update = loc.split('/')[-1] 64 | print('updating', event_to_update) 65 | r = update_event(eventid, event_to_update) 66 | if r.status_code >= 400: 67 | print(r.status_code, r.headers) 68 | else: 69 | print(r.status_code, r.headers) 70 | print(eventid, 'done.') 71 | 72 | 73 | def export_our_org(): 74 | circl = source.search(org='CIRCL') 75 | return circl 76 | 77 | if __name__ == '__main__': 78 | import argparse 79 | parser = argparse.ArgumentParser( 80 | description='Copy the events from one MISP instance to an other.') 81 | parser.add_argument('-f', '--filename', type=str, 82 | help='File containing a list of event id.') 83 | parser.add_argument( 84 | '-l', '--loop', action='store_true', 85 | help='Endless loop: eventid in the terminal and it will be copied.') 86 | parser.add_argument('--priv_to_cert', action='store_false', default=True, 87 | help='Copy from MISP priv to MISP CERT.') 88 | args = parser.parse_args() 89 | init(args.priv_to_cert) 90 | if args.filename is not None: 91 | list_copy(args.filename) 92 | else: 93 | loop_copy() 94 | -------------------------------------------------------------------------------- /examples/covid19/import_dxy_covid19_live.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pathlib import Path 5 | from pymisp import MISPEvent, MISPOrganisation, PyMISP 6 | from dateutil.parser import parse 7 | import json 8 | from pymisp.tools import feed_meta_generator 9 | from io import BytesIO 10 | 11 | make_feed = False 12 | 13 | path = Path('/home/raphael/gits/covid-19-china/data') 14 | 15 | 16 | if make_feed: 17 | org = MISPOrganisation() 18 | org.name = 'CIRCL' 19 | org.uuid = "55f6ea5e-2c60-40e5-964f-47a8950d210f" 20 | else: 21 | from covid_key import url, key 22 | misp = PyMISP(url, key) 23 | 24 | for p in path.glob('*_json/current_china.json'): 25 | d = parse(p.parent.name[:-5]) 26 | event = MISPEvent() 27 | event.info = f"[{d.isoformat()}] DXY COVID-19 live report" 28 | event.date = d 29 | event.distribution = 3 30 | event.add_tag('tlp:white') 31 | if make_feed: 32 | event.orgc = org 33 | else: 34 | e = misp.search(eventinfo=event.info, metadata=True, pythonify=True) 35 | if e: 36 | # Already added. 37 | continue 38 | event.add_attribute('attachment', p.name, data=BytesIO(p.open('rb').read())) 39 | with p.open() as f: 40 | data = json.load(f) 41 | for province in data: 42 | obj_province = event.add_object(name='covid19-dxy-live-province', standalone=False) 43 | obj_province.add_attribute('province', province['provinceName']) 44 | obj_province.add_attribute('update', d) 45 | if province['currentConfirmedCount']: 46 | obj_province.add_attribute('current-confirmed', province['currentConfirmedCount']) 47 | if province['confirmedCount']: 48 | obj_province.add_attribute('total-confirmed', province['confirmedCount']) 49 | if province['curedCount']: 50 | obj_province.add_attribute('total-cured', province['curedCount']) 51 | if province['deadCount']: 52 | obj_province.add_attribute('total-death', province['deadCount']) 53 | if province['comment']: 54 | obj_province.add_attribute('comment', province['comment']) 55 | 56 | for city in province['cities']: 57 | obj_city = event.add_object(name='covid19-dxy-live-city', standalone=False) 58 | obj_city.add_attribute('city', city['cityName']) 59 | obj_city.add_attribute('update', d) 60 | if city['currentConfirmedCount']: 61 | obj_city.add_attribute('current-confirmed', city['currentConfirmedCount']) 62 | if city['confirmedCount']: 63 | obj_city.add_attribute('total-confirmed', city['confirmedCount']) 64 | if city['curedCount']: 65 | obj_city.add_attribute('total-cured', city['curedCount']) 66 | if city['deadCount']: 67 | obj_city.add_attribute('total-death', city['deadCount']) 68 | obj_city.add_reference(obj_province, 'part-of') 69 | 70 | if make_feed: 71 | with (Path('output') / f'{event.uuid}.json').open('w') as _w: 72 | json.dump(event.to_feed(), _w) 73 | else: 74 | misp.add_event(event) 75 | 76 | if make_feed: 77 | feed_meta_generator(Path('output')) 78 | -------------------------------------------------------------------------------- /examples/create_events.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP, MISPEvent 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Create an event on MISP.') 11 | parser.add_argument("-d", "--distrib", type=int, help="The distribution setting used for the attributes and for the newly created event, if relevant. [0-3].") 12 | parser.add_argument("-i", "--info", help="Used to populate the event info field if no event ID supplied.") 13 | parser.add_argument("-a", "--analysis", type=int, help="The analysis level of the newly created event, if applicable. [0-2]") 14 | parser.add_argument("-t", "--threat", type=int, help="The threat level ID of the newly created event, if applicable. [1-4]") 15 | args = parser.parse_args() 16 | 17 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 18 | 19 | event = MISPEvent() 20 | event.distribution = args.distrib 21 | event.threat_level_id = args.threat 22 | event.analysis = args.analysis 23 | event.info = args.info 24 | 25 | event = misp.add_event(event, pythonify=True) 26 | print(event) 27 | -------------------------------------------------------------------------------- /examples/del.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Delete an event from a MISP instance.') 11 | parser.add_argument("-e", "--event", help="Event ID to delete.") 12 | parser.add_argument("-a", "--attribute", help="Attribute ID to delete.") 13 | 14 | args = parser.parse_args() 15 | 16 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 17 | 18 | if args.event: 19 | result = misp.delete_event(args.event) 20 | else: 21 | result = misp.delete_attribute(args.attribute) 22 | print(result) 23 | -------------------------------------------------------------------------------- /examples/delete_user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Delete the user with the given id. Keep in mind that disabling users (by setting the disabled flag via an edit) is always preferred to keep user associations to events intact.') 11 | parser.add_argument("-i", "--user_id", help="The id of the user you want to delete.") 12 | args = parser.parse_args() 13 | 14 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 15 | 16 | print(misp.delete_user(args.user_id)) 17 | -------------------------------------------------------------------------------- /examples/edit_organisation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP, MISPOrganisation 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | if __name__ == '__main__': 9 | parser = argparse.ArgumentParser(description='Edit the email of the organisation designed by the organisation_id.') 10 | parser.add_argument("-i", "--organisation_id", required=True, help="The name of the json file describing the organisation you want to modify.") 11 | parser.add_argument("-e", "--email", help="Email linked to the organisation.") 12 | args = parser.parse_args() 13 | 14 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 15 | 16 | org = MISPOrganisation() 17 | org.id = args.organisation_id 18 | org.email = args.email 19 | 20 | print(misp.update_organisation(org, pythonify=True)) 21 | -------------------------------------------------------------------------------- /examples/edit_user.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP, MISPUser 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Edit the email of the user designed by the user_id.') 11 | parser.add_argument("-i", "--user_id", required=True, help="The name of the json file describing the user you want to modify.") 12 | parser.add_argument("-e", "--email", help="Email linked to the account.") 13 | args = parser.parse_args() 14 | 15 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 16 | user = MISPUser 17 | user.id = args.user_id 18 | user.email = args.email 19 | 20 | print(misp.edit_user(user, pythonify=True)) 21 | -------------------------------------------------------------------------------- /examples/events/README.md: -------------------------------------------------------------------------------- 1 | ## Explanation 2 | 3 | This folder contains scripts made to create dummy events in order to test MISP instances. 4 | 5 | * dummy is a containing text only file used as uploaded attachement. 6 | * create\_dummy\_event.py will create a given number of events (default: 1)with a randomly generated domain|ip attribute as well as a copy of dummy file. 7 | * create\_massive\_dummy\_events.py will create a given number of events (default: 1) with a given number of randomly generated attributes(default: 3000). 8 | 9 | ### Tools description 10 | 11 | * randomStringGenerator: generate a random string of a given size, characters used to build the string can be chosen, default are characters from string.ascii\_lowercase and string.digits 12 | * randomIpGenerator: generate a random ip 13 | 14 | * floodtxt: add a generated string as attribute of the given event. The added attributes can be of the following category/type: 15 | - Internal reference/comment 16 | - Internal reference/text 17 | - Internal reference/other 18 | - Payload delivery/email-subject 19 | - Artifact dropped/mutex 20 | - Artifact dropped/filename 21 | * floodip: add a generated ip as attribute of the given event. The added attributes can be of the following category/type: 22 | - Network activity/ip-src 23 | - Network activity/ip.dst 24 | * flooddomain: add a generated domain-like string as attribute of the given event. The added attributes can be of the following category/type: 25 | - Network activity/hostname 26 | - Network activity/domain 27 | * flooddomainip: add a generated domain|ip-like string as attribute of the given event. The added attribute is of the following category/type: 28 | - Network activity/domain|ip 29 | * floodemail: add a generated email-like string as attribute of the given event. The added attributes can be of the following category/type: 30 | - Payload delivery/email-src 31 | - Payload delivery/email-dst 32 | * floodattachmentent: add a dummy file as attribute of the given event. The added attribute is of the following category/type: 33 | - Payload delivery/attachment 34 | 35 | * create\_dummy\_event: create a dummy event named "dummy event" with these caracteristics: 36 | - Distribution: Your organisation only 37 | - Analysis: Initial 38 | - Threat Level: Undefined 39 | - Number of Attributes: 2 40 | - Attribute: 41 | - category/type: Network activity/domain|ip 42 | - value: Randomly generated 43 | - Attribute: 44 | -category/type: Payload delivery/attachment 45 | - value: 'dummy' file 46 | * create\_massive\_dummy\_events: create a dummy event named "massive dummy event" with these caracteristics: 47 | - Distribution: Your organisation only 48 | - Analysis: Initial 49 | - Threat Level: Undefined 50 | - Number of Attributes: Given as argument 51 | - Attribute: 52 | - category/type: Randomly chosen 53 | - value: Randomly generated or dummy file 54 | -------------------------------------------------------------------------------- /examples/events/create_dummy_event.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | import tools 8 | 9 | def init(url, key): 10 | return PyMISP(url, key, misp_verifycert, 'json') 11 | 12 | if __name__ == '__main__': 13 | parser = argparse.ArgumentParser(description='Create a given number of event containing an domain|ip attribute and an attachment each.') 14 | parser.add_argument("-l", "--limit", type=int, help="Number of events to create (default 1)") 15 | args = parser.parse_args() 16 | 17 | misp = init(misp_url, misp_key) 18 | 19 | if args.limit is None: 20 | args.limit = 1 21 | 22 | for i in range(args.limit): 23 | tools.create_dummy_event(misp) 24 | -------------------------------------------------------------------------------- /examples/events/create_massive_dummy_events.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | try: 6 | from keys import url, key 7 | verifycert = False 8 | except ImportError: 9 | url = 'https://localhost:8443' 10 | key = 'd6OmdDFvU3Seau3UjwvHS1y3tFQbaRNhJhDX0tjh' 11 | verifycert = False 12 | import argparse 13 | import tools 14 | 15 | 16 | if __name__ == '__main__': 17 | parser = argparse.ArgumentParser(description='Create a given number of event containing a given number of attributes eachh.') 18 | parser.add_argument("-l", "--limit", type=int, help="Number of events to create (default 1)") 19 | parser.add_argument("-a", "--attribute", type=int, help="Number of attributes per event (default 3000)") 20 | args = parser.parse_args() 21 | 22 | misp = PyMISP(url, key, verifycert) 23 | misp.toggle_global_pythonify() 24 | 25 | if args.limit is None: 26 | args.limit = 1 27 | if args.attribute is None: 28 | args.attribute = 3000 29 | 30 | for i in range(args.limit): 31 | tools.create_massive_dummy_events(misp, args.attribute) 32 | -------------------------------------------------------------------------------- /examples/events/dummy: -------------------------------------------------------------------------------- 1 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 2 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 3 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 4 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 5 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 6 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 7 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 8 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 9 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 10 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 11 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 12 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 13 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 14 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 15 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 16 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 17 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 18 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 19 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 20 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 21 | DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY DUMMY 22 | -------------------------------------------------------------------------------- /examples/events/tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import random 5 | from random import randint 6 | import string 7 | from pymisp import MISPEvent, MISPAttribute 8 | 9 | 10 | def randomStringGenerator(size, chars=string.ascii_lowercase + string.digits): 11 | return ''.join(random.choice(chars) for _ in range(size)) 12 | 13 | 14 | def randomIpGenerator(): 15 | return str(randint(0, 255)) + '.' + str(randint(0, 255)) + '.' + str(randint(0, 255)) + '.' + str(randint(0, 255)) 16 | 17 | 18 | def _attribute(category, type, value): 19 | attribute = MISPAttribute() 20 | attribute.category = category 21 | attribute.type = type 22 | attribute.value = value 23 | return attribute 24 | 25 | 26 | def floodtxt(misp, event, maxlength=255): 27 | text = randomStringGenerator(randint(1, maxlength)) 28 | choose_from = [('Internal reference', 'comment', text), ('Internal reference', 'text', text), 29 | ('Internal reference', 'other', text), ('Network activity', 'email-subject', text), 30 | ('Artifacts dropped', 'mutex', text), ('Artifacts dropped', 'filename', text)] 31 | misp.add_attribute(event, _attribute(*random.choice(choose_from))) 32 | 33 | 34 | def floodip(misp, event): 35 | ip = randomIpGenerator() 36 | choose_from = [('Network activity', 'ip-src', ip), ('Network activity', 'ip-dst', ip)] 37 | misp.add_attribute(event, _attribute(*random.choice(choose_from))) 38 | 39 | 40 | def flooddomain(misp, event, maxlength=25): 41 | a = randomStringGenerator(randint(1, maxlength)) 42 | b = randomStringGenerator(randint(2, 3), chars=string.ascii_lowercase) 43 | domain = a + '.' + b 44 | choose_from = [('Network activity', 'domain', domain), ('Network activity', 'hostname', domain)] 45 | misp.add_attribute(event, _attribute(*random.choice(choose_from))) 46 | 47 | 48 | def floodemail(misp, event, maxlength=25): 49 | a = randomStringGenerator(randint(1, maxlength)) 50 | b = randomStringGenerator(randint(1, maxlength)) 51 | c = randomStringGenerator(randint(2, 3), chars=string.ascii_lowercase) 52 | email = a + '@' + b + '.' + c 53 | choose_from = [('Network activity', 'email-dst', email), ('Network activity', 'email-src', email)] 54 | misp.add_attribute(event, _attribute(*random.choice(choose_from))) 55 | 56 | 57 | def create_dummy_event(misp): 58 | event = MISPEvent() 59 | event.info = 'Dummy event' 60 | event = misp.add_event(event, pythonify=True) 61 | return event 62 | 63 | 64 | def create_massive_dummy_events(misp, nbattribute): 65 | event = MISPEvent() 66 | event.info = 'massive dummy event' 67 | event = misp.add_event(event) 68 | print(event) 69 | functions = [floodtxt, floodip, flooddomain, floodemail] 70 | for i in range(nbattribute): 71 | functions[random.randint(0, len(functions) - 1)](misp, event) 72 | -------------------------------------------------------------------------------- /examples/feed-generator-from-redis/ObjectConstructor/CowrieMISPObject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import time 4 | 5 | from pymisp.tools.abstractgenerator import AbstractMISPObjectGenerator 6 | 7 | 8 | class CowrieMISPObject(AbstractMISPObjectGenerator): 9 | def __init__(self, dico_val, **kargs): 10 | self._dico_val = dico_val 11 | # Enforce attribute date with timestamp 12 | super(CowrieMISPObject, self).__init__('cowrie', 13 | default_attributes_parameters={'timestamp': int(time.time())}, 14 | **kargs) 15 | self.generate_attributes() 16 | 17 | def generate_attributes(self): 18 | valid_object_attributes = self._definition['attributes'].keys() 19 | for object_relation, value in self._dico_val.items(): 20 | if object_relation not in valid_object_attributes: 21 | continue 22 | 23 | if object_relation == 'timestamp': 24 | # Date already in ISO format, removing trailing Z 25 | value = value.rstrip('Z') 26 | 27 | if isinstance(value, dict): 28 | self.add_attribute(object_relation, **value) 29 | else: 30 | # uniformize value, sometimes empty array 31 | if isinstance(value, list) and len(value) == 0: 32 | value = '' 33 | self.add_attribute(object_relation, value=value) 34 | -------------------------------------------------------------------------------- /examples/feed-generator-from-redis/README.md: -------------------------------------------------------------------------------- 1 | # Generic MISP feed generator 2 | ## Description 3 | 4 | - ``generator.py`` exposes a class allowing to generate a MISP feed in real time, where each items can be added on daily generated events. 5 | - ``fromredis.py`` uses ``generator.py`` to generate a MISP feed based on data stored in redis. 6 | - ``server.py`` is a simple script using *Flask_autoindex* to serve data to MISP. 7 | - ``MISPItemToRedis.py`` permits to push (in redis) items to be added in MISP by the ``fromredis.py`` script. 8 | 9 | 10 | ## Installation 11 | 12 | ``` 13 | # redis-server 14 | sudo apt install redis-server 15 | 16 | # Check if redis is running 17 | redis-cli ping 18 | 19 | # Feed generator 20 | git clone https://github.com/MISP/PyMISP 21 | cd PyMISP/examples/feed-generator-from-redis 22 | cp settings.default.py settings.py 23 | vi settings.py # adjust your settings 24 | 25 | python3 fromredis.py 26 | 27 | # Serving file to MISP 28 | bash install.sh 29 | . ./serv-env/bin/activate 30 | python3 server.py 31 | ```` 32 | 33 | 34 | ## Usage 35 | 36 | ``` 37 | # Activate virtualenv 38 | . ./serv-env/bin/activate 39 | ``` 40 | 41 | ### Adding items to MISP 42 | 43 | ``` 44 | # create helper object 45 | >>> helper = MISPItemToRedis("redis_list_keyname") 46 | 47 | # push an attribute to redis 48 | >>> helper.push_attribute("ip-src", "8.8.8.8", category="Network activity") 49 | 50 | # push an object to redis 51 | >>> helper.push_object({ "name": "cowrie", "session": "session_id", "username": "admin", "password": "admin", "protocol": "telnet" }) 52 | 53 | # push a sighting to redis 54 | >>> helper.push_sighting(uuid="5a9e9e26-fe40-4726-8563-5585950d210f") 55 | ``` 56 | 57 | ### Generate the feed 58 | 59 | ``` 60 | # Create the FeedGenerator object using the configuration provided in the file settings.py 61 | # It will create daily event in which attributes and object will be added 62 | >>> generator = FeedGenerator() 63 | 64 | # Add an attribute to the daily event 65 | >>> attr_type = "ip-src" 66 | >>> attr_value = "8.8.8.8" 67 | >>> additional_data = {} 68 | >>> generator.add_attribute_to_event(attr_type, attr_value, **additional_data) 69 | 70 | # Add a cowrie object to the daily event 71 | >>> obj_name = "cowrie" 72 | >>> obj_data = { "session": "session_id", "username": "admin", "password": "admin", "protocol": "telnet" } 73 | >>> generator.add_object_to_event(obj_name, **obj_data) 74 | 75 | # Immediately write the event to the disk (Bypassing the default flushing behavior) 76 | >>> generator.flush_event() 77 | ``` 78 | 79 | ### Consume stored data in redis 80 | 81 | ``` 82 | # Configuration provided in the file settings.py 83 | >>> python3 fromredis.py 84 | ``` 85 | 86 | ### Serve data to MISP 87 | 88 | ``` 89 | >>> python3 server.py 90 | ``` 91 | -------------------------------------------------------------------------------- /examples/feed-generator-from-redis/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | virtualenv -p python3 serv-env 3 | . ./serv-env/bin/activate 4 | pip3 install -U flask Flask-AutoIndex redis 5 | -------------------------------------------------------------------------------- /examples/feed-generator-from-redis/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os.path 4 | from flask import Flask 5 | from flask_autoindex import AutoIndex 6 | from settings import outputdir 7 | 8 | app = Flask(__name__) 9 | AutoIndex(app, browse_root=os.path.join(os.path.curdir, outputdir)) 10 | 11 | if __name__ == '__main__': 12 | app.run(host='0.0.0.0') 13 | -------------------------------------------------------------------------------- /examples/feed-generator-from-redis/settings.default.py: -------------------------------------------------------------------------------- 1 | """ REDIS RELATED """ 2 | # Your redis server 3 | host='127.0.0.1' 4 | port=6379 5 | db=0 6 | ## The keynames to POP element from 7 | keyname_pop=['cowrie'] 8 | 9 | # OTHERS 10 | ## If key prefix not provided, data will be added as either object, attribute or sighting 11 | fallback_MISP_type = 'object' 12 | ### How to handle the fallback 13 | fallback_object_template_name = 'cowrie' # MISP-Object only 14 | fallback_attribute_category = 'comment' # MISP-Attribute only 15 | 16 | ## How frequent the event should be written on disk 17 | flushing_interval=5*60 18 | ## The redis list keyname in which to put items that generated an error 19 | keyname_error='feed-generation-error' 20 | 21 | """ FEED GENERATOR CONFIGURATION """ 22 | 23 | # The output dir for the feed. This will drop a lot of files, so make 24 | # sure that you use a directory dedicated to the feed 25 | outputdir = 'output' 26 | 27 | # Event meta data 28 | ## Required 29 | ### The organisation id that generated this feed 30 | org_name='myOrg' 31 | ### Your organisation UUID 32 | org_uuid='' 33 | ### The daily event name to be used in MISP. 34 | ### (e.g. honeypot_1, will produce each day an event of the form honeypot_1 dd-mm-yyyy) 35 | daily_event_name='PyMISP default event name' 36 | 37 | ## Optional 38 | analysis=0 39 | threat_level_id=3 40 | published=False 41 | Tag=[ 42 | { 43 | "colour": "#ffffff", 44 | "name": "tlp:white" 45 | }, 46 | { 47 | "colour": "#ff00ff", 48 | "name": "my:custom:feed" 49 | } 50 | ] 51 | 52 | # MISP Object constructor 53 | from ObjectConstructor.CowrieMISPObject import CowrieMISPObject 54 | from pymisp.tools import GenericObjectGenerator 55 | 56 | constructor_dict = { 57 | 'cowrie': CowrieMISPObject, 58 | 'generic': GenericObjectGenerator 59 | } 60 | 61 | # Others 62 | ## Redis pooling time 63 | sleep=60 64 | -------------------------------------------------------------------------------- /examples/feed-generator/README.md: -------------------------------------------------------------------------------- 1 | # What 2 | 3 | This python script can be used to generate a MISP feed based on an existing MISP instance. 4 | 5 | # Installation 6 | 7 | ```` 8 | git clone https://github.com/MISP/PyMISP.git 9 | cd examples/feed-generator 10 | cp settings.default.py settings.py 11 | vi settings.py #adjust your settings 12 | python3 generate.py 13 | ```` 14 | 15 | # Output 16 | 17 | The generated feed will be stored in your `outputdir`. 18 | It contains the files: 19 | - `manifest.json` - containing the feed manifest (generic event information) 20 | - `hashes.csv` - listing the hashes of the attribute values 21 | - `*.json` - a large amount of `json` files 22 | 23 | 24 | # Importing in MISP 25 | 26 | To import this feed into your MISP instance: 27 | - Sync Actions > List Feeds > Add feed 28 | - Fill in the form while ensuring the 'source format' is set to 'MISP Feed' 29 | 30 | For more information about feeds please read: https://misp.gitbooks.io/misp-book/content/managing-feeds/ 31 | -------------------------------------------------------------------------------- /examples/feed-generator/output/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/examples/feed-generator/output/empty -------------------------------------------------------------------------------- /examples/feed-generator/settings.default.py: -------------------------------------------------------------------------------- 1 | # Your MISP's URL 2 | url = '' 3 | 4 | # The auth key to the MISP user that you wish to use. Make sure that the 5 | # user has auth_key access 6 | key = '' 7 | 8 | # Should the certificate be validated? 9 | ssl = False 10 | 11 | # The output dir for the feed. This will drop a lot of files, so make 12 | # sure that you use a directory dedicated to the feed 13 | outputdir = 'output' 14 | 15 | # The filters to be used for by the feed. You can use any filter that 16 | # you can use on the event index, such as organisation, tags, etc. 17 | # It uses the same joining and condition rules as the API parameters 18 | # For example: 19 | # filters = {'tags':['tlp:white','feed-export','!privint'],'org':'CIRCL', 'published':1} 20 | # the above would generate a feed for all published events created by CIRCL, 21 | # tagged tlp:white and/or feed-export but exclude anything tagged privint 22 | filters = {'published':'true'} 23 | 24 | # Include deleted attributes and objects in the events 25 | include_deleted = False 26 | 27 | # By default all attributes will be included in the feed generation 28 | # Remove the levels that you do not wish to include in the feed 29 | # Use this to further narrow down what gets exported, for example: 30 | # Setting this to ['3', '5'] will exclude any attributes from the feed that 31 | # are not exportable to all or inherit the event 32 | # 33 | # The levels are as follows: 34 | # 0: Your Organisation Only 35 | # 1: This Community Only 36 | # 2: Connected Communities 37 | # 3: All 38 | # 4: Sharing Group 39 | # 5: Inherit Event 40 | valid_attribute_distribution_levels = ['0', '1', '2', '3', '4', '5'] 41 | 42 | # By default, all attribute passing the filtering rules will be exported. 43 | # This setting can be used to filter out any attributes being of the type contained in the list. 44 | # Warning: Keep in mind that if you propagate data (via synchronisation/feeds/...), recipients 45 | # will not be able to get these attributes back unless their events get updated. 46 | # For example: 47 | # exclude_attribute_types = ['malware-sample'] 48 | exclude_attribute_types = [] 49 | 50 | # Include the distribution and sharing group information (and names/UUIDs of organisations in those Sharing Groups) 51 | # Set this to False if you want to discard the distribution metadata. That way all data will inherit the distribution 52 | # the feed 53 | with_distribution = False 54 | 55 | # Include the exportable local tags along with the global tags. The default is True. 56 | with_local_tags = True 57 | 58 | # Include signatures for protected events. This will allow MISP to ingest and update protected events from the feed. It requires perm_server_sign to be set to true in the user's role on MISP's side. 59 | with_signatures = False -------------------------------------------------------------------------------- /examples/fetch_events_feed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from keys import misp_url, misp_key, misp_verifycert 5 | import argparse 6 | from pymisp import PyMISP 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Fetch all events from a feed.') 11 | parser.add_argument("-f", "--feed", required=True, help="feed's ID to be fetched.") 12 | args = parser.parse_args() 13 | 14 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 15 | misp.fetch_feed(args.feed) 16 | -------------------------------------------------------------------------------- /examples/fetch_warninglist_hits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key 6 | import argparse 7 | 8 | 9 | def init(url, key): 10 | return PyMISP(url, key) 11 | 12 | 13 | def loop_attributes(elem): 14 | if 'Attribute' in elem.keys(): 15 | for attribute in elem['Attribute']: 16 | if 'warnings' in attribute.keys(): 17 | for warning in attribute['warnings']: 18 | print("Value {} has a hit in warninglist with name '{}' and id '{}'".format(warning['value'], 19 | warning[ 20 | 'warninglist_name'], 21 | warning[ 22 | 'warninglist_id'])) 23 | 24 | 25 | if __name__ == '__main__': 26 | parser = argparse.ArgumentParser(description='Print all warninglist hits for an event.') 27 | parser.add_argument("eventid", type=str, help="The event id of the event to get info of") 28 | args = parser.parse_args() 29 | misp = init(misp_url, misp_key) 30 | evt = misp.search('events', eventid=args.eventid, includeWarninglistHits=1)['response'][0]['Event'] 31 | if 'warnings' in evt.keys(): 32 | print('warnings in entire event:') 33 | print(str(evt['warnings']) + '\n') 34 | print('Warnings at attribute levels:') 35 | loop_attributes(evt) 36 | if 'Object' in evt.keys(): 37 | for obj in evt['Object']: 38 | loop_attributes(obj) 39 | -------------------------------------------------------------------------------- /examples/freetext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | from io import open 9 | 10 | 11 | if __name__ == '__main__': 12 | parser = argparse.ArgumentParser(description="Update a MISP event.") 13 | parser.add_argument("-e", "--event", required=True, help="Event ID to update.") 14 | parser.add_argument("-i", "--input", required=True, help="Input file") 15 | 16 | args = parser.parse_args() 17 | 18 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert) 19 | 20 | with open(args.input, 'r') as f: 21 | result = pymisp.freetext(args.event, f.read()) 22 | print(result) 23 | -------------------------------------------------------------------------------- /examples/freetext.txt: -------------------------------------------------------------------------------- 1 | 8.8.8.8 2 | 3 | google.fr 4 | 5 | https://gmail.com 6 | -------------------------------------------------------------------------------- /examples/generate_file_objects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | import json 6 | 7 | try: 8 | from pymisp import pymisp_json_default, AbstractMISP 9 | from pymisp.tools import make_binary_objects 10 | except ImportError: 11 | pass 12 | 13 | 14 | def check(): 15 | missing_dependencies = {'pydeep': False, 'lief': False, 'magic': False, 'pymisp': False} 16 | try: 17 | import pymisp # noqa 18 | except ImportError: 19 | missing_dependencies['pymisp'] = 'Please install pydeep: pip install pymisp' 20 | try: 21 | import pydeep # noqa 22 | except ImportError: 23 | missing_dependencies['pydeep'] = 'Please install pydeep: pip install git+https://github.com/kbandla/pydeep.git' 24 | try: 25 | import lief # noqa 26 | except ImportError: 27 | missing_dependencies['lief'] = 'Please install lief, documentation here: https://github.com/lief-project/LIEF' 28 | try: 29 | import magic # noqa 30 | except ImportError: 31 | missing_dependencies['magic'] = 'Please install python-magic: pip install python-magic.' 32 | return json.dumps(missing_dependencies) 33 | 34 | 35 | def make_objects(path): 36 | to_return = {'objects': [], 'references': []} 37 | fo, peo, seos = make_binary_objects(path) 38 | 39 | if seos: 40 | for s in seos: 41 | to_return['objects'].append(s) 42 | if s.ObjectReference: 43 | to_return['references'] += s.ObjectReference 44 | 45 | if peo: 46 | if hasattr(peo, 'certificates') and hasattr(peo, 'signers'): 47 | # special authenticode case for PE objects 48 | for c in peo.certificates: 49 | to_return['objects'].append(c) 50 | for s in peo.signers: 51 | to_return['objects'].append(s) 52 | del peo.certificates 53 | del peo.signers 54 | del peo.sections 55 | to_return['objects'].append(peo) 56 | if peo.ObjectReference: 57 | to_return['references'] += peo.ObjectReference 58 | 59 | if fo: 60 | to_return['objects'].append(fo) 61 | if fo.ObjectReference: 62 | to_return['references'] += fo.ObjectReference 63 | return json.dumps(to_return, default=pymisp_json_default) 64 | 65 | 66 | if __name__ == '__main__': 67 | parser = argparse.ArgumentParser(description='Extract indicators out of binaries and returns MISP objects.') 68 | group = parser.add_mutually_exclusive_group() 69 | group.add_argument("-p", "--path", help="Path to process.") 70 | group.add_argument("-c", "--check", action='store_true', help="Check the dependencies.") 71 | args = parser.parse_args() 72 | a = AbstractMISP() 73 | 74 | if args.check: 75 | print(check()) 76 | if args.path: 77 | obj = make_objects(args.path) 78 | print(obj) 79 | -------------------------------------------------------------------------------- /examples/generate_meta_feed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp.tools import feed_meta_generator 5 | import argparse 6 | from pathlib import Path 7 | 8 | if __name__ == '__main__': 9 | parser = argparse.ArgumentParser(description='Build meta files for feed') 10 | parser.add_argument("--feed", required=True, help="Path to directory containing the feed.") 11 | args = parser.parse_args() 12 | 13 | feed = Path(args.feed) 14 | 15 | feed_meta_generator(feed) 16 | -------------------------------------------------------------------------------- /examples/get.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | import os 8 | 9 | 10 | proxies = { 11 | 'http': 'http://127.0.0.1:8123', 12 | 'https': 'http://127.0.0.1:8123', 13 | } 14 | 15 | proxies = None 16 | 17 | 18 | if __name__ == '__main__': 19 | 20 | parser = argparse.ArgumentParser(description='Get an event from a MISP instance.') 21 | parser.add_argument("-e", "--event", required=True, help="Event ID to get.") 22 | parser.add_argument("-o", "--output", help="Output file") 23 | 24 | args = parser.parse_args() 25 | 26 | if args.output is not None and os.path.exists(args.output): 27 | print('Output file already exists, abort.') 28 | exit(0) 29 | 30 | misp = PyMISP(misp_url, misp_key, misp_verifycert, proxies=proxies) 31 | 32 | event = misp.get_event(args.event, pythonify=True) 33 | if args.output: 34 | with open(args.output, 'w') as f: 35 | f.write(event.to_json()) 36 | else: 37 | print(event.to_json()) 38 | -------------------------------------------------------------------------------- /examples/get_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | 6 | from pymisp import PyMISP 7 | from keys import misp_url, misp_key, misp_verifycert 8 | 9 | 10 | if __name__ == '__main__': 11 | parser = argparse.ArgumentParser(description='Get MISP stuff as CSV.') 12 | parser.add_argument("--controller", default='attributes', help="Attribute to use for the search (events, objects, attributes)") 13 | parser.add_argument("-e", "--event_id", help="Event ID to fetch. Without it, it will fetch the whole database.") 14 | parser.add_argument("-a", "--attribute", nargs='+', help="Attribute column names") 15 | parser.add_argument("-o", "--object_attribute", nargs='+', help="Object attribute column names") 16 | parser.add_argument("-t", "--misp_types", nargs='+', help="MISP types to fetch (ip-src, hostname, ...)") 17 | parser.add_argument("-c", "--context", action='store_true', help="Add event level context (tags...)") 18 | parser.add_argument("-f", "--outfile", help="Output file to write the CSV.") 19 | 20 | args = parser.parse_args() 21 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert, debug=True) 22 | attr = [] 23 | if args.attribute: 24 | attr += args.attribute 25 | if args.object_attribute: 26 | attr += args.object_attribute 27 | if not attr: 28 | attr = None 29 | print(args.context) 30 | response = pymisp.search(return_format='csv', controller=args.controller, eventid=args.event_id, requested_attributes=attr, 31 | type_attribute=args.misp_types, include_context=args.context) 32 | 33 | if args.outfile: 34 | with open(args.outfile, 'w') as f: 35 | f.write(response) 36 | else: 37 | print(response) 38 | -------------------------------------------------------------------------------- /examples/get_network_activity.event_id: -------------------------------------------------------------------------------- 1 | 2 2 | 1 3 | 3 4 | 4 -------------------------------------------------------------------------------- /examples/graphdb/make_neo4j.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from pymisp import Neo4j 6 | from pymisp import MISPEvent 7 | from keys import misp_url, misp_key 8 | import argparse 9 | 10 | """ 11 | Sample Neo4J query: 12 | 13 | 14 | MATCH ()-[r:has]->(n) 15 | WITH n, count(r) as rel_cnt 16 | WHERE rel_cnt > 5 17 | MATCH (m)-[r:has]->(n) 18 | RETURN m, n LIMIT 200; 19 | """ 20 | 21 | if __name__ == '__main__': 22 | parser = argparse.ArgumentParser(description='Get all the events matching a value.') 23 | parser.add_argument("-s", "--search", required=True, help="String to search.") 24 | parser.add_argument("--host", default='localhost:7474', help="Host where neo4j is running.") 25 | parser.add_argument("-u", "--user", default='neo4j', help="User on neo4j.") 26 | parser.add_argument("-p", "--password", default='neo4j', help="Password on neo4j.") 27 | parser.add_argument("-d", "--deleteall", action="store_true", default=False, help="Delete all nodes from the database") 28 | args = parser.parse_args() 29 | 30 | neo4j = Neo4j(args.host, args.user, args.password) 31 | if args.deleteall: 32 | neo4j.del_all() 33 | misp = PyMISP(misp_url, misp_key) 34 | result = misp.search_all(args.search) 35 | for json_event in result['response']: 36 | if not json_event['Event']: 37 | print(json_event) 38 | continue 39 | print('Importing', json_event['Event']['info'], json_event['Event']['id']) 40 | try: 41 | misp_event = MISPEvent() 42 | misp_event.load(json_event) 43 | neo4j.import_event(misp_event) 44 | except: 45 | print('broken') 46 | -------------------------------------------------------------------------------- /examples/ioc_2_misp/README.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | Python script for ioc import to misp 4 | 5 | ### requires 6 | 7 | > python 2.7 8 | > PyMISP 9 | > BeautifulSoup (apt-get install python-bs4 python-lxml) 10 | 11 | ### Usage 12 | 13 | ```bash 14 | python ioc2misp.py -i myioc -t "tag:mytag='sample','tag:other='foo'" 15 | ``` 16 | 17 | ```bash 18 | time find /iocsample -type f|while read line ;do python ioc2misp.py -i ${line};done 19 | ``` 20 | 21 | ### Conf 22 | 23 | * rename keys.py.sample as keys.py 24 | * add your url and api key in keys.py 25 | * use command in terminal 26 | -------------------------------------------------------------------------------- /examples/ioc_2_misp/keys.py.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | mispUrl = '' 5 | mispKey = '' 6 | 7 | ############################### 8 | # file use for internal tag 9 | # some sample can be find here : 10 | # https://github.com/eset/malware-ioc 11 | # https://github.com/fireeye/iocs 12 | csvTaxonomyFile = "taxonomy.csv" 13 | 14 | # csv delimiter : ";" with quotechar : " 15 | 16 | ############################### 17 | # link sample 18 | #~ 19 | #~ APT 20 | #~ APT12 21 | #~ Backdoor 22 | #~ Apache 2.0 23 | #~ 24 | 25 | # @link from csv 26 | # = rel attribut from 27 | # @value from csv 28 | # = value 29 | # @keep 30 | # 0 : don't create tag 31 | # 1 : tag created 32 | # @taxonomy 33 | # define tag for misp 34 | # @comment 35 | # litte description but not use 36 | 37 | 38 | ######################################### 39 | # https://www.circl.lu/doc/misp/categories-and-types/index.html 40 | # /\ 41 | # || 42 | # || 43 | # \/ 44 | # http://schemas.mandiant.com/ 45 | 46 | # @index = Context/search form ioc 47 | # @(1, 2, 3) 48 | # 1. categorie mapping 49 | # 2. type mapping 50 | # 3. optionnal comment 51 | 52 | 53 | iocMispMapping = { 54 | 55 | ('DriverItem/DriverName') : (u'Artifacts dropped',u'other', u'DriverName. '), 56 | 57 | ('DnsEntryItem/Host') : (u'Network activity',u'domain'), 58 | 59 | ('Email/To') : (u'Targeting data',u'target-email'), 60 | ('Email/Date') : (u'Other',u'comment',u'EmailDate. '), 61 | ('Email/Body') : (u'Payload delivery',u'email-subject'), 62 | ('Email/From') : (u'Payload delivery',u'email-dst'), 63 | ('Email/Subject') : (u'Payload delivery',u'email-subject'), 64 | ('Email/Attachment/Name') : (u'Payload delivery',u'email-attachment'), 65 | 66 | ('FileItem/Md5sum') : (u'External analysis',u'md5'), 67 | ('FileItem/Sha1sum') : (u'External analysis',u'sha1'), 68 | ('FileItem/FileName') : (u'External analysis',u'filename'), 69 | ('FileItem/FullPath') : (u'External analysis',u'filename'), 70 | ('FileItem/FilePath') : (u'External analysis',u'filename'), 71 | ('FileItem/Sha256sum') : (u'External analysis',u'sha256'), 72 | 73 | ('Network/URI') : (u'Network activity',u'uri'), 74 | ('Network/DNS') : (u'Network activity',u'domain'), 75 | ('Network/String') : (u'Network activity',u'ip-dst'), 76 | ('Network/UserAgent') : (u'Network activity',u'user-agent'), 77 | 78 | ('PortItem/localIP') : (u'Network activity',u'ip-dst'), 79 | 80 | ('ProcessItem/name') : (u'External analysis',u'pattern-in-memory', u'ProcessName. '), 81 | ('ProcessItem/path') : (u'External analysis',u'pattern-in-memory', u'ProcessPath. '), 82 | ('ProcessItem/Mutex') : (u'Artifacts dropped',u'mutex', u'mutex'), 83 | ('ProcessItem/Pipe/Name') : (u'Artifacts dropped',u'named pipe'), 84 | ('ProcessItem/Mutex/Name') : (u'Artifacts dropped',u'mutex', u'MutexName. '), 85 | 86 | ('RegistryItem/Text') : (u'Artifacts dropped',u'regkey', u'RegistryText. '), 87 | ('RegistryItem/Path') : (u'Artifacts dropped',u'regkey', u'RegistryPath. '), 88 | 89 | ('ServiceItem/name') : (u'Artifacts dropped',u'windows-service-name'), 90 | ('ServiceItem/type') : (u'Artifacts dropped',u'pattern-in-memory', u'ServiceType. '), 91 | 92 | ('Snort/Snort') : (u'Network activity',u'snort'), 93 | 94 | } 95 | -------------------------------------------------------------------------------- /examples/ioc_2_misp/taxonomy.csv: -------------------------------------------------------------------------------- 1 | link,value,keep,taxonomy,comment 2 | classification,TLP AMBER,1,tlp:amber, 3 | classification,TLP GREEN,1,tlp:green, 4 | confidential,TLP-AMBER,1,tlp:amber, 5 | confidential,TLP GREEN,1,tlp:green, 6 | confidential,TLP-GREEN,1,tlp:green, 7 | confidential,TLP RED,1,tlp:red, 8 | exportable,Yes,0,, 9 | family,APT,1,malware_classification:malware-category='APT', 10 | family,APT3,1,malware_classification:malware-category='APT3',https://github.com/fireeye/iocs/tree/master/APT3 11 | license,Apache 2.0,0,, 12 | threatcategory,APT3,1,malware_classification:malware-category='APT3',https://github.com/fireeye/iocs/tree/master/APT3 13 | -------------------------------------------------------------------------------- /examples/keys.py.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | misp_url = 'https:///' 5 | misp_key = 'Your MISP auth key' # The MISP auth key can be found on the MISP web interface under the automation section 6 | misp_verifycert = True 7 | misp_client_cert = '' 8 | proofpoint_sp = '' # Service Principal from TAP (https://threatinsight.proofpoint.com//settings/connected-applications) 9 | proofpoint_secret = '' -------------------------------------------------------------------------------- /examples/last.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | try: 7 | from keys import misp_client_cert 8 | except ImportError: 9 | misp_client_cert = '' 10 | import argparse 11 | import os 12 | 13 | 14 | # Usage for pipe masters: ./last.py -l 5h | jq . 15 | # Usage in case of large data set and pivoting page by page: python3 last.py -l 48h -m 10 -p 2 | jq .[].Event.info 16 | 17 | if __name__ == '__main__': 18 | parser = argparse.ArgumentParser(description='Download latest events from a MISP instance.') 19 | parser.add_argument("-l", "--last", required=True, help="can be defined in days, hours, minutes (for example 5d or 12h or 30m).") 20 | parser.add_argument("-m", "--limit", required=False, default="10", help="Add the limit of records to get (by default, the limit is set to 10)") 21 | parser.add_argument("-p", "--page", required=False, default="1", help="Add the page to request to paginate over large dataset (by default page is set to 1)") 22 | parser.add_argument("-o", "--output", help="Output file") 23 | 24 | args = parser.parse_args() 25 | 26 | if args.output is not None and os.path.exists(args.output): 27 | print('Output file already exists, aborted.') 28 | exit(0) 29 | 30 | if misp_client_cert == '': 31 | misp_client_cert = None 32 | else: 33 | misp_client_cert = (misp_client_cert) 34 | 35 | misp = PyMISP(misp_url, misp_key, misp_verifycert, cert=misp_client_cert) 36 | result = misp.search(publish_timestamp=args.last, limit=args.limit, page=args.page, pythonify=True) 37 | 38 | if not result: 39 | print('No results for that time period') 40 | exit(0) 41 | 42 | if args.output: 43 | with open(args.output, 'w') as f: 44 | for r in result: 45 | f.write(r.to_json() + '\n') 46 | else: 47 | for r in result: 48 | print(r.to_json()) 49 | -------------------------------------------------------------------------------- /examples/lookup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp.tools import ext_lookups 5 | import argparse 6 | 7 | 8 | if __name__ == '__main__': 9 | 10 | parser = argparse.ArgumentParser(description='Search is galaxies or taxonomies.') 11 | parser.add_argument("-q", "--query", help="Query.") 12 | 13 | args = parser.parse_args() 14 | 15 | tag_gal = ext_lookups.revert_tag_from_galaxies(args.query) 16 | tag_tax = ext_lookups.revert_tag_from_taxonomies(args.query) 17 | 18 | found_tax = ext_lookups.search_taxonomies(args.query) 19 | found_gal = ext_lookups.search_galaxies(args.query) 20 | 21 | if tag_gal: 22 | print(tag_gal) 23 | if tag_tax: 24 | print(tag_tax) 25 | if found_tax: 26 | print(found_tax) 27 | if found_gal: 28 | print(found_gal) 29 | -------------------------------------------------------------------------------- /examples/misp2cef.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Export IOC's from MISP in CEF format 5 | # Based on cef_export.py MISP module by Hannah Ward 6 | 7 | import sys 8 | import datetime 9 | from pymisp import PyMISP, MISPAttribute 10 | from keys import misp_url, misp_key, misp_verifycert 11 | 12 | cefconfig = {"Default_Severity":1, "Device_Vendor":"MISP", "Device_Product":"MISP", "Device_Version":1} 13 | 14 | cefmapping = {"ip-src":"src", "ip-dst":"dst", "hostname":"dhost", "domain":"destinationDnsDomain", 15 | "md5":"fileHash", "sha1":"fileHash", "sha256":"fileHash", 16 | "filename|md5":"fileHash", "filename|sha1":"fileHash", "filename|sha256":"fileHash", 17 | "url":"request"} 18 | 19 | mispattributes = {'input':list(cefmapping.keys())} 20 | 21 | 22 | def make_cef(event): 23 | for attr in event["Attribute"]: 24 | if attr["to_ids"] and attr["type"] in cefmapping: 25 | if '|' in attr["type"] and '|' in attr["value"]: 26 | value = attr["value"].split('|')[1] 27 | else: 28 | value = attr["value"] 29 | response = "{} host CEF:0|{}|{}|{}|{}|{}|{}|msg={} customerURI={} externalId={} {}={}".format( 30 | datetime.datetime.fromtimestamp(int(attr["timestamp"])).strftime("%b %d %H:%M:%S"), 31 | cefconfig["Device_Vendor"], 32 | cefconfig["Device_Product"], 33 | cefconfig["Device_Version"], 34 | attr["category"], 35 | attr["category"], 36 | cefconfig["Default_Severity"], 37 | event["info"].replace("\\","\\\\").replace("=","\\=").replace('\n','\\n') + "(MISP Event #" + event["id"] + ")", 38 | misp_url + 'events/view/' + event["id"], 39 | attr["uuid"], 40 | cefmapping[attr["type"]], 41 | value, 42 | ) 43 | print(str(bytes(response, 'utf-8'), 'utf-8')) 44 | 45 | 46 | def init_misp(): 47 | global mymisp 48 | mymisp = PyMISP(misp_url, misp_key, misp_verifycert) 49 | 50 | 51 | def echeck(r): 52 | if r.get('errors'): 53 | if r.get('message') == 'No matches.': 54 | return 55 | else: 56 | print(r['errors']) 57 | sys.exit(1) 58 | 59 | 60 | def find_events(): 61 | r = mymisp.search(controller='events', published=True, to_ids=True) 62 | echeck(r) 63 | if not r.get('response'): 64 | return 65 | for ev in r['response']: 66 | make_cef(ev['Event']) 67 | 68 | 69 | if __name__ == '__main__': 70 | init_misp() 71 | find_events() 72 | -------------------------------------------------------------------------------- /examples/misp2clamav.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # vim: tabstop=4 shiftwidth=4 expandtab 4 | # 5 | # Export file hashes from MISP to ClamAV hdb file 6 | 7 | import sys 8 | from pymisp import PyMISP, MISPAttribute 9 | from keys import misp_url, misp_key, misp_verifycert 10 | 11 | 12 | def init_misp(): 13 | global mymisp 14 | mymisp = PyMISP(misp_url, misp_key, misp_verifycert) 15 | 16 | 17 | def echeck(r): 18 | if r.get('errors'): 19 | if r.get('message') == 'No matches.': 20 | return 21 | else: 22 | print(r['errors']) 23 | sys.exit(1) 24 | 25 | 26 | def find_hashes(htype): 27 | r = mymisp.search(controller='attributes', type_attribute=htype) 28 | echeck(r) 29 | if not r.get('response'): 30 | return 31 | for a in r['response']['Attribute']: 32 | attribute = MISPAttribute(mymisp.describe_types) 33 | attribute.from_dict(**a) 34 | if '|' in attribute.type and '|' in attribute.value: 35 | c, value = attribute.value.split('|') 36 | comment = '{} - {}'.format(attribute.comment, c) 37 | else: 38 | comment = attribute.comment 39 | value = attribute.value 40 | mhash = value.replace(':', ';') 41 | mfile = 'MISP event {} {}'.format(a['event_id'], comment.replace(':', ';').replace('\r', '').replace('\n', '')) 42 | print('{}:*:{}:73'.format(mhash, mfile)) 43 | 44 | 45 | if __name__ == '__main__': 46 | init_misp() 47 | find_hashes('md5') 48 | find_hashes('sha1') 49 | find_hashes('sha256') 50 | find_hashes('filename|md5') 51 | find_hashes('filename|sha1') 52 | find_hashes('filename|sha256') 53 | -------------------------------------------------------------------------------- /examples/openioc_to_misp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | 6 | from pymisp import PyMISP 7 | from keys import misp_url, misp_key, misp_verifycert 8 | from pymisp.tools import load_openioc_file 9 | 10 | 11 | if __name__ == '__main__': 12 | parser = argparse.ArgumentParser(description='Convert an OpenIOC file to a MISPEvent. Optionnaly send it to MISP.') 13 | parser.add_argument("-i", "--input", required=True, help="Input file") 14 | group = parser.add_mutually_exclusive_group(required=True) 15 | group.add_argument("-o", "--output", help="Output file") 16 | group.add_argument("-m", "--misp", action='store_true', help="Create new event on MISP") 17 | 18 | args = parser.parse_args() 19 | 20 | misp_event = load_openioc_file(args.input) 21 | 22 | if args.misp: 23 | pymisp = PyMISP(misp_url, misp_key, misp_verifycert, debug=True) 24 | pymisp.add_event(misp_event) 25 | else: 26 | with open(args.output, 'w') as f: 27 | f.write(misp_event.to_json()) 28 | -------------------------------------------------------------------------------- /examples/profiles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/examples/profiles/__init__.py -------------------------------------------------------------------------------- /examples/profiles/daily_report.py: -------------------------------------------------------------------------------- 1 | types_to_attach = ['ip-dst', 'url', 'domain'] 2 | objects_to_attach = ['domain-ip'] 3 | 4 | headers = """ 5 | :toc: right 6 | :toclevels: 1 7 | :toc-title: Daily Report 8 | :icons: font 9 | :sectanchors: 10 | :sectlinks: 11 | = Daily report by {org_name} 12 | {date} 13 | 14 | :icons: font 15 | 16 | """ 17 | 18 | event_level_tags = """ 19 | IMPORTANT: This event is classified TLP:{value}. 20 | 21 | {expanded} 22 | 23 | """ 24 | 25 | attributes = """ 26 | === Indicator(s) of compromise 27 | 28 | {list_attributes} 29 | 30 | """ 31 | 32 | title = """ 33 | == ({internal_id}) {title} 34 | 35 | {summary} 36 | 37 | """ 38 | -------------------------------------------------------------------------------- /examples/profiles/weekly_report.py: -------------------------------------------------------------------------------- 1 | types_to_attach = ['ip-dst', 'url', 'domain', 'md5'] 2 | objects_to_attach = ['domain-ip', 'file'] 3 | 4 | headers = """ 5 | :toc: right 6 | :toclevels: 1 7 | :toc-title: Weekly Report 8 | :icons: font 9 | :sectanchors: 10 | :sectlinks: 11 | = Weekly report by {org_name} 12 | {date} 13 | 14 | :icons: font 15 | 16 | """ 17 | 18 | event_level_tags = """ 19 | """ 20 | 21 | attributes = """ 22 | === Indicator(s) of compromise 23 | 24 | {list_attributes} 25 | 26 | """ 27 | 28 | title = """ 29 | == ({internal_id}) {title} 30 | 31 | {summary} 32 | 33 | """ 34 | -------------------------------------------------------------------------------- /examples/proofpoint_vap.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from pymisp import PyMISP, MISPEvent, MISPOrganisation 4 | from keys import misp_url, misp_key, misp_verifycert, proofpoint_key 5 | 6 | # initialize PyMISP and set url for Panorama 7 | misp = PyMISP(url=misp_url, key=misp_key, ssl=misp_verifycert) 8 | 9 | urlVap = "https://tap-api-v2.proofpoint.com/v2/people/vap?window=30" # Window can be 14, 30, and 90 Days 10 | 11 | headers = { 12 | 'Authorization': "Basic " + proofpoint_key 13 | } 14 | 15 | responseVap = requests.request("GET", urlVap, headers=headers) 16 | 17 | jsonDataVap = json.loads(responseVap.text) 18 | 19 | for alert in jsonDataVap["users"]: 20 | orgc = MISPOrganisation() 21 | orgc.name = 'Proofpoint' 22 | orgc.id = '#{ORGC.ID}' # organisation id 23 | orgc.uuid = '#{ORGC.UUID}' # organisation uuid 24 | # initialize and set MISPEvent() 25 | event = MISPEvent() 26 | event.Orgc = orgc 27 | event.info = 'Very Attacked Person ' + jsonDataVap["interval"] 28 | event.distribution = 0 # Optional, defaults to MISP.default_event_distribution in MISP config 29 | event.threat_level_id = 2 # setting this to 0 breaks the integration 30 | event.analysis = 0 # Optional, defaults to 0 (initial analysis) 31 | 32 | totalVapUsers = event.add_attribute('counter', jsonDataVap["totalVapUsers"], comment="Total VAP Users") 33 | 34 | averageAttackIndex = event.add_attribute('counter', jsonDataVap["averageAttackIndex"], comment="Average Attack Count") 35 | 36 | vapAttackIndexThreshold = event.add_attribute('counter', jsonDataVap["vapAttackIndexThreshold"], comment="Attack Threshold") 37 | 38 | emails = event.add_attribute('email-dst', alert["identity"]["emails"], comment="Email Destination") 39 | 40 | attack = event.add_attribute('counter', alert["threatStatistics"]["attackIndex"], comment="Attack Count") 41 | 42 | vip = event.add_attribute('other', str(alert["identity"]["vip"]), comment="VIP") 43 | 44 | guid = event.add_attribute('other', alert["identity"]["guid"], comment="GUID") 45 | 46 | if alert["identity"]["customerUserId"] is not None: 47 | customerUserId = event.add_attribute('other', alert["identity"]["customerUserId"], comment="Customer User Id") 48 | 49 | if alert["identity"]["department"] is not None: 50 | department = event.add_attribute(alert['other', "identity"]["department"], comment="Department") 51 | 52 | if alert["identity"]["location"] is not None: 53 | location = event.add_attribute('other', alert["identity"]["location"], comment="Location") 54 | 55 | if alert["identity"]["name"] is not None: 56 | 57 | name = event.add_attribute('target-user', alert["identity"]["name"], comment="Name") 58 | 59 | if alert["identity"]["title"] is not None: 60 | 61 | title = event.add_attribute('other', alert["identity"]["title"], comment="Title") 62 | 63 | event.add_tag("VAP") 64 | 65 | misp.add_event(event.to_json()) 66 | -------------------------------------------------------------------------------- /examples/search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key,misp_verifycert 6 | import argparse 7 | import os 8 | import json 9 | 10 | 11 | def init(url, key): 12 | return PyMISP(url, key, misp_verifycert, 'json') 13 | 14 | 15 | def search(m, quiet, url, controller, out=None, **kwargs): 16 | result = m.search(controller, **kwargs) 17 | if quiet: 18 | for e in result['response']: 19 | print('{}{}{}\n'.format(url, '/events/view/', e['Event']['id'])) 20 | elif out is None: 21 | print(json.dumps(result['response'])) 22 | else: 23 | with open(out, 'w') as f: 24 | f.write(json.dumps(result['response'])) 25 | 26 | if __name__ == '__main__': 27 | parser = argparse.ArgumentParser(description='Get all the events matching a value for a given param.') 28 | parser.add_argument("-p", "--param", required=True, help="Parameter to search (e.g. category, org, values, type_attribute, etc.)") 29 | parser.add_argument("-s", "--search", required=True, help="String to search.") 30 | parser.add_argument("-a", "--attributes", action='store_true', help="Search attributes instead of events") 31 | parser.add_argument("-q", "--quiet", action='store_true', help="Only display URLs to MISP") 32 | parser.add_argument("-o", "--output", help="Output file") 33 | 34 | args = parser.parse_args() 35 | 36 | if args.output is not None and os.path.exists(args.output): 37 | print('Output file already exists, abort.') 38 | exit(0) 39 | 40 | misp = init(misp_url, misp_key) 41 | kwargs = {args.param: args.search} 42 | 43 | if args.attributes: 44 | controller='attributes' 45 | else: 46 | controller='events' 47 | 48 | search(misp, args.quiet, misp_url, controller, args.output, **kwargs) 49 | -------------------------------------------------------------------------------- /examples/search_attributes_yara.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Example of specifying special attribute type in your search: here yara attribute 4 | 5 | from pymisp import PyMISP 6 | from keys import misp_url, misp_key,misp_verifycert 7 | import argparse 8 | import os 9 | import json 10 | 11 | def init(url, key): 12 | return PyMISP(url, key, misp_verifycert, 'json') 13 | 14 | def search(m, quiet, url, out=None, custom_type_attribute="yara"): 15 | controller='attributes' 16 | result = m.search(controller, type_attribute = custom_type_attribute) 17 | if quiet: 18 | for e in result['response']: 19 | print('{}{}{}\n'.format(url, '/events/view/', e['Event']['id'])) 20 | elif out is None: 21 | print(json.dumps(result['response'])) 22 | else: 23 | with open(out, 'w') as f: 24 | f.write(json.dumps(result['response'])) 25 | 26 | 27 | if __name__ == '__main__': 28 | parser = argparse.ArgumentParser(description='Get all the events matching a value for a given param.') 29 | parser.add_argument("-q", "--quiet", action='store_true', help="Only display URLs to MISP") 30 | parser.add_argument("-o", "--output", help="Output file") 31 | 32 | args = parser.parse_args() 33 | 34 | if args.output is not None and os.path.exists(args.output): 35 | print('Output file already exists, abort.') 36 | exit(0) 37 | 38 | misp = init(misp_url, misp_key) 39 | 40 | search(misp, args.quiet, misp_url, args.output) 41 | -------------------------------------------------------------------------------- /examples/search_sighting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | import os 8 | import json 9 | 10 | 11 | def init(url, key): 12 | return PyMISP(url, key, misp_verifycert, 'json') 13 | 14 | 15 | def search_sighting(m, context, out=None, **kwargs): 16 | 17 | result = m.search_sightings(context, **kwargs) 18 | if out is None: 19 | print(json.dumps(result['response'])) 20 | else: 21 | with open(out, 'w') as f: 22 | f.write(json.dumps(result['response'])) 23 | 24 | 25 | if __name__ == '__main__': 26 | parser = argparse.ArgumentParser(description='Get all the events matching a value.') 27 | parser.add_argument("-c", "--context", default="", help="Context in which to search. Could be empty, attribute or event") 28 | parser.add_argument("-i", "--id", type=int, help="If context is set, the ID in which the search should be done") 29 | parser.add_argument("-o", "--output", help="Output file") 30 | 31 | args = parser.parse_args() 32 | 33 | if args.output is not None and os.path.exists(args.output): 34 | print('Output file already exists, abord.') 35 | exit(0) 36 | 37 | misp = init(misp_url, misp_key) 38 | kwargs = {} 39 | if len(args.context) > 0: 40 | kwargs['id'] = args.id 41 | 42 | search_sighting(misp, args.context, args.output, **kwargs) 43 | -------------------------------------------------------------------------------- /examples/server_sync_check_conn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import requests 4 | import json 5 | 6 | # Suppress those "Unverified HTTPS request is being made" 7 | import urllib3 8 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 9 | 10 | from keys import misp_url, misp_key, misp_verifycert 11 | proxies = { 12 | 13 | } 14 | 15 | ''' 16 | Checks if the connection to a sync server works 17 | returns json object 18 | ''' 19 | 20 | def check_connection(connection_number): 21 | 22 | misp_headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': misp_key} 23 | req = requests.get(misp_url + 'servers/testConnection/{}'.format(connection_number), verify=misp_verifycert, headers=misp_headers, proxies=proxies) 24 | 25 | result = json.loads(req.text) 26 | return(result) 27 | 28 | 29 | if __name__ == "__main__": 30 | 31 | result = check_connection(1) 32 | print(result) 33 | -------------------------------------------------------------------------------- /examples/sharing_groups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Get a list of the sharing groups from the MISP instance.') 11 | 12 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 13 | 14 | sharing_groups = misp.sharing_groups(pythonify=True) 15 | print(sharing_groups) 16 | -------------------------------------------------------------------------------- /examples/sighting.json: -------------------------------------------------------------------------------- 1 | {"values":["www.google.com", "8.8.8.8"], "timestamp":1460558710} 2 | 3 | -------------------------------------------------------------------------------- /examples/situational_awareness/README.md: -------------------------------------------------------------------------------- 1 | ## Explanation 2 | 3 | * treemap.py is a script that will generate an interactive svg (attribute\_treemap.svg) containing a treepmap representing the distribution of attributes in a sample (data) fetched from the instance using "last" or "searchall" examples. 4 | * It will also generate a html document with a table (attribute\_table.html) containing count for each type of attribute. 5 | * test\_attribute\_treemap.html is a quick page made to visualize both treemap and table at the same time. 6 | 7 | * tags\_count.py is a script that count the number of occurrences of every tags in a fetched sample of Events in a given period of time. 8 | * tag\_search.py is a script that count the number of occurrences of a given tag in a fetched sample of Events in a given period of time. 9 | * Events will be fetched from _days_ days ago to today. 10 | * _begindate_ is the beginning of the studied period. If it is later than today, an error will be raised. 11 | * _enddate_ is the end of the studied period. If it is earlier than _begindate_, an error will be raised. 12 | * tag\_search.py allows research for multiple tags is possible by separating each tag by the | symbol. 13 | * Partial research is also possible with tag\_search.py. For instance, search for "ransom" will also return tags containin "ransomware". 14 | 15 | * tags\_to\_graphs.py is a script that will generate several plots to visualise tags distribution. 16 | * The studied _period_ can be either the 7, 28 or 360 last days 17 | * _accuracy_ allows to get smallers splits of data instead of the default values 18 | * _order_ define the accuracy of the curve fitting. Default value is 3 19 | * It will generate two plots comparing all the tags: 20 | * tags_repartition_plot that present the raw data 21 | * tags_repartition_trend_plot that present the general evolution for each tag 22 | * Then each taxonomies will be represented in three plots: 23 | * Raw datas: in "plot" folder, named with the name of the corresponding taxonomy 24 | * Trend: in "plot" folder, named _taxonomy_\_trend. general evolution of the data (linear fitting, curve fitting at order 1) 25 | * Curve fitting: in "plotlib" folder, name as the taxonomy it presents. 26 | * In order to visualize the last plots, a html file is also generated automaticaly (might be improved in the future) 27 | 28 | :warning: These scripts are not time optimised 29 | 30 | ## Requierements 31 | 32 | * [Pygal](https://github.com/Kozea/pygal/) 33 | * [Matplotlib](https://github.com/matplotlib/matplotlib) 34 | * [Pandas](https://github.com/pandas-dev/pandas) 35 | * [SciPy](https://github.com/scipy/scipy) 36 | * [PyTaxonomies](https://github.com/MISP/PyTaxonomies) 37 | * [Python3-tk](https://github.com/python-git/python/blob/master/Lib/lib-tk/Tkinter.py) 38 | 39 | -------------------------------------------------------------------------------- /examples/situational_awareness/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/examples/situational_awareness/__init__.py -------------------------------------------------------------------------------- /examples/situational_awareness/attribute_treemap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | import tools 8 | import pygal_tools 9 | 10 | if __name__ == '__main__': 11 | parser = argparse.ArgumentParser(description='Take a sample of events (based on last.py of searchall.py) and create a treemap epresenting the distribution of attributes in this sample.') 12 | parser.add_argument("-f", "--function", required=True, help='The parameter can be either set to "last" or "searchall". If the parameter is not valid, "last" will be the default setting.') 13 | parser.add_argument("-a", "--argument", required=True, help='if function is "last", time can be defined in days, hours, minutes (for example 5d or 12h or 30m). Otherwise, this argument is the string to search') 14 | 15 | args = parser.parse_args() 16 | 17 | misp = PyMISP(misp_url, misp_key, misp_verifycert, 'json') 18 | 19 | if args.function == "searchall": 20 | result = misp.search_all(args.argument) 21 | else: 22 | result = misp.download_last(args.argument) 23 | 24 | if 'response' in result: 25 | events = tools.eventsListBuildFromArray(result) 26 | attributes = tools.attributesListBuild(events) 27 | temp = tools.getNbAttributePerEventCategoryType(attributes) 28 | temp = temp.groupby(level=['category', 'type']).sum() 29 | pygal_tools.createTreemap(temp, 'Attributes Distribution', 'attribute_treemap.svg', 'attribute_table.html') 30 | else: 31 | print ('There is no event answering the research criteria') 32 | -------------------------------------------------------------------------------- /examples/situational_awareness/bokeh_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from bokeh.plotting import figure, output_file, show, ColumnDataSource 5 | from bokeh.models import HoverTool 6 | import date_tools 7 | 8 | 9 | def tagsDistributionScatterPlot(NbTags, dates, plotname='Tags Distribution Plot'): 10 | 11 | output_file(plotname + ".html") 12 | 13 | counts = {} 14 | glyphs = {} 15 | desc = {} 16 | hover = HoverTool() 17 | plot = figure(plot_width=800, plot_height=800, x_axis_type="datetime", x_axis_label='Date', y_axis_label='Number of tags', tools=[hover]) 18 | 19 | for name in NbTags.keys(): 20 | desc[name] = [] 21 | for date in dates[name]: 22 | desc[name].append(date_tools.datetimeToString(date, "%Y-%m-%d")) 23 | counts[name] = plot.circle(dates[name], NbTags[name], legend="Number of events with y tags", source=ColumnDataSource( 24 | data=dict( 25 | desc=desc[name] 26 | ) 27 | )) 28 | glyphs[name] = counts[name].glyph 29 | glyphs[name].size = int(name) * 2 30 | hover.tooltips = [("date", "@desc")] 31 | if int(name) != 0: 32 | glyphs[name].fill_alpha = 1/int(name) 33 | show(plot) 34 | -------------------------------------------------------------------------------- /examples/situational_awareness/date_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from datetime import datetime 5 | from datetime import timedelta 6 | from dateutil.parser import parse 7 | 8 | 9 | class DateError(Exception): 10 | def __init__(self, value): 11 | self.value = value 12 | 13 | def __str__(self): 14 | return repr(self.value) 15 | 16 | 17 | # ############### Date Tools ################ 18 | 19 | def dateInRange(datetimeTested, begin=None, end=None): 20 | if begin is None: 21 | begin = datetime(1970, 1, 1) 22 | if end is None: 23 | end = datetime.now() 24 | return begin <= datetimeTested <= end 25 | 26 | 27 | def toDatetime(date): 28 | return parse(date) 29 | 30 | 31 | def datetimeToString(datetime, formatstring): 32 | return datetime.strftime(formatstring) 33 | 34 | 35 | def checkDateConsistancy(begindate, enddate, lastdate): 36 | if begindate is not None and enddate is not None: 37 | if begindate > enddate: 38 | raise DateError('begindate ({}) cannot be after enddate ({})'.format(begindate, enddate)) 39 | 40 | if enddate is not None: 41 | if toDatetime(enddate) < lastdate: 42 | raise DateError('enddate ({}) cannot be before lastdate ({})'.format(enddate, lastdate)) 43 | 44 | if begindate is not None: 45 | if toDatetime(begindate) > datetime.now(): 46 | raise DateError('begindate ({}) cannot be after today ({})'.format(begindate, datetime.now().date())) 47 | 48 | 49 | def setBegindate(begindate, lastdate): 50 | return max(begindate, lastdate) 51 | 52 | 53 | def setEnddate(enddate): 54 | return min(enddate, datetime.now()) 55 | 56 | 57 | def getLastdate(last): 58 | return (datetime.now() - timedelta(days=int(last))).replace(hour=0, minute=0, second=0, microsecond=0) 59 | 60 | 61 | def getNDaysBefore(date, days): 62 | return (date - timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0) 63 | 64 | 65 | def getToday(): 66 | return (datetime.now()).replace(hour=0, minute=0, second=0, microsecond=0) 67 | 68 | 69 | def days_between(date_1, date_2): 70 | return abs((date_2 - date_1).days) 71 | -------------------------------------------------------------------------------- /examples/situational_awareness/pygal_tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import pygal 5 | from pygal.style import Style 6 | import pandas 7 | import random 8 | 9 | 10 | def createTable(colors, categ_types_hash, tablename='attribute_table.html'): 11 | with open(tablename, 'w') as target: 12 | target.write('\n\n\n\n\n') 13 | for categ_name, types in categ_types_hash.items(): 14 | table = pygal.Treemap(pretty_print=True) 15 | target.write('\n

{}

\n'.format(colors[categ_name], categ_name)) 16 | for d in types: 17 | table.add(d['label'], d['value']) 18 | target.write(table.render_table(transpose=True)) 19 | target.write('\n\n') 20 | 21 | 22 | def createTreemap(data, title, treename='attribute_treemap.svg', tablename='attribute_table.html'): 23 | labels_categ = data.index.labels[0] 24 | labels_types = data.index.labels[1] 25 | names_categ = data.index.levels[0] 26 | names_types = data.index.levels[1] 27 | categ_types_hash = {} 28 | for categ_id, type_val, total in zip(labels_categ, labels_types, data): 29 | if not categ_types_hash.get(names_categ[categ_id]): 30 | categ_types_hash[names_categ[categ_id]] = [] 31 | dict_to_print = {'label': names_types[type_val], 'value': total} 32 | categ_types_hash[names_categ[categ_id]].append(dict_to_print) 33 | 34 | colors = {categ: "#%06X" % random.randint(0, 0xFFFFFF) for categ in categ_types_hash.keys()} 35 | style = Style(background='transparent', 36 | plot_background='#FFFFFF', 37 | foreground='#111111', 38 | foreground_strong='#111111', 39 | foreground_subtle='#111111', 40 | opacity='.6', 41 | opacity_hover='.9', 42 | transition='400ms ease-in', 43 | colors=tuple(colors.values())) 44 | 45 | treemap = pygal.Treemap(pretty_print=True, legend_at_bottom=True, style=style) 46 | treemap.title = title 47 | treemap.print_values = True 48 | treemap.print_labels = True 49 | 50 | for categ_name, types in categ_types_hash.items(): 51 | treemap.add(categ_name, types) 52 | 53 | createTable(colors, categ_types_hash) 54 | treemap.render_to_file(treename) 55 | -------------------------------------------------------------------------------- /examples/situational_awareness/style.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | /*font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;*/ 4 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 5 | } 6 | 7 | h1 8 | { 9 | font-size: 16px; 10 | width: 290px; 11 | text-align:center; 12 | } 13 | 14 | /*** Stats Tables ***/ 15 | 16 | table 17 | { 18 | border-collapse: collapse; 19 | border-spacing: 0; 20 | border: 1px solid #cbcbcb; 21 | } 22 | 23 | tbody 24 | { 25 | font-size:12px; 26 | } 27 | 28 | table td 29 | { 30 | border-left: 1px solid #cbcbcb; 31 | border-width: 0 0 0 1px; 32 | width: 500px; 33 | margin: 0; 34 | padding: 0.5em 1em; 35 | } 36 | 37 | .test 38 | { 39 | width: 500px; 40 | } 41 | 42 | table tr:nth-child(2n-1) td 43 | { 44 | background-color: #f2f2f2; 45 | } 46 | 47 | table tr td:first-child 48 | { 49 | font-weight: bold; 50 | } 51 | -------------------------------------------------------------------------------- /examples/situational_awareness/style2.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | /*font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;*/ 4 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 5 | } 6 | 7 | h1 8 | { 9 | font-size: 16px; 10 | width: 290px; 11 | text-align:center; 12 | } 13 | 14 | /*** Stats Tables ***/ 15 | 16 | table 17 | { 18 | border-collapse: collapse; 19 | border-spacing: 0; 20 | table-layout: fixed; 21 | width: 6000px; 22 | border: 1px solid #cbcbcb; 23 | } 24 | 25 | tbody 26 | { 27 | font-size:12px; 28 | } 29 | 30 | td 31 | { 32 | border-left: 1px solid #cbcbcb; 33 | border-width: 0 0 0 1px; 34 | margin: 0; 35 | padding: 0.5em 1em; 36 | } 37 | 38 | table tr td:first-child 39 | { 40 | font-weight: bold; 41 | } 42 | -------------------------------------------------------------------------------- /examples/situational_awareness/tag_scatter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | import numpy 8 | import tools 9 | import date_tools 10 | import bokeh_tools 11 | 12 | import time 13 | 14 | if __name__ == '__main__': 15 | parser = argparse.ArgumentParser(description='Show the evolution of trend of tags.') 16 | parser.add_argument("-d", "--days", type=int, required=True, help='') 17 | parser.add_argument("-s", "--begindate", required=True, help='format yyyy-mm-dd') 18 | parser.add_argument("-e", "--enddate", required=True, help='format yyyy-mm-dd') 19 | 20 | args = parser.parse_args() 21 | 22 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 23 | 24 | result = misp.search(date_from=args.begindate, date_to=args.enddate, metadata=False) 25 | 26 | # Getting data 27 | 28 | if 'response' in result: 29 | events = tools.eventsListBuildFromArray(result) 30 | NbTags = [] 31 | dates = [] 32 | enddate = date_tools.toDatetime(args.enddate) 33 | begindate = date_tools.toDatetime(args.begindate) 34 | 35 | for i in range(round(date_tools.days_between(enddate, begindate)/args.days)): 36 | begindate = date_tools.getNDaysBefore(enddate, args.days) 37 | eventstemp = tools.selectInRange(events, begindate, enddate) 38 | if eventstemp is not None: 39 | for event in eventstemp.iterrows(): 40 | if 'Tag' in event[1]: 41 | dates.append(enddate) 42 | if isinstance(event[1]['Tag'], list): 43 | NbTags.append(len(event[1]['Tag'])) 44 | else: 45 | NbTags.append(0) 46 | enddate = begindate 47 | 48 | # Prepare plot 49 | 50 | NbTagsPlot = {} 51 | datesPlot = {} 52 | 53 | for i in range(len(NbTags)): 54 | if NbTags[i] == -1: 55 | continue 56 | count = 1 57 | for j in range(i+1, len(NbTags)): 58 | if NbTags[i] == NbTags[j] and dates[i] == dates[j]: 59 | count = count + 1 60 | NbTags[j] = -1 61 | if str(count) in NbTagsPlot: 62 | NbTagsPlot[str(count)].append(NbTags[i]) 63 | datesPlot[str(count)].append(dates[i]) 64 | else: 65 | NbTagsPlot[str(count)] = [NbTags[i]] 66 | datesPlot[str(count)] = [dates[i]] 67 | NbTags[i] = -1 68 | 69 | # Plot 70 | 71 | bokeh_tools.tagsDistributionScatterPlot(NbTagsPlot, datesPlot) 72 | -------------------------------------------------------------------------------- /examples/situational_awareness/tag_search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | from datetime import datetime 7 | import argparse 8 | import tools 9 | import date_tools 10 | 11 | 12 | def init(url, key): 13 | return PyMISP(url, key, misp_verifycert, 'json') 14 | 15 | # ######### fetch data ########## 16 | 17 | 18 | if __name__ == '__main__': 19 | parser = argparse.ArgumentParser(description='Take a sample of events (based on last.py) and give the number of occurrence of the given tag in this sample.') 20 | parser.add_argument("-t", "--tag", required=True, help="tag to search (search for multiple tags is possible by using |. example : \"osint|OSINT\")") 21 | parser.add_argument("-d", "--days", type=int, help="number of days before today to search. If not define, default value is 7") 22 | parser.add_argument("-b", "--begindate", help="The research will look for tags attached to events posted at or after the given startdate (format: yyyy-mm-dd): If no date is given, default time is epoch time (1970-1-1)") 23 | parser.add_argument("-e", "--enddate", help="The research will look for tags attached to events posted at or before the given enddate (format: yyyy-mm-dd): If no date is given, default time is now()") 24 | 25 | args = parser.parse_args() 26 | 27 | misp = init(misp_url, misp_key) 28 | 29 | if args.days is None: 30 | args.days = 7 31 | result = misp.search(last='{}d'.format(args.days), metadata=True) 32 | 33 | date_tools.checkDateConsistancy(args.begindate, args.enddate, date_tools.getLastdate(args.days)) 34 | 35 | if args.begindate is None: 36 | args.begindate = date_tools.getLastdate(args.days) 37 | else: 38 | args.begindate = date_tools.setBegindate(date_tools.toDatetime(args.begindate), tools.getLastdate(args.days)) 39 | 40 | if args.enddate is None: 41 | args.enddate = datetime.now() 42 | else: 43 | args.enddate = date_tools.setEnddate(date_tools.toDatetime(args.enddate)) 44 | 45 | if 'response' in result: 46 | events = tools.selectInRange(tools.eventsListBuildFromArray(result), begin=args.begindate, end=args.enddate) 47 | totalPeriodEvents = tools.getNbitems(events) 48 | tags = tools.tagsListBuild(events) 49 | result = tools.isTagIn(tags, args.tag) 50 | totalPeriodTags = len(result) 51 | 52 | text = 'Studied pediod: from ' 53 | if args.begindate is None: 54 | text = text + '1970-01-01' 55 | else: 56 | text = text + str(args.begindate.date()) 57 | text = text + ' to ' 58 | if args.enddate is None: 59 | text = text + str(datetime.now().date()) 60 | else: 61 | text = text + str(args.enddate.date()) 62 | 63 | print('\n========================================================') 64 | print(text) 65 | print('During the studied pediod, ' + str(totalPeriodTags) + ' events out of ' + str(totalPeriodEvents) + ' contains at least one tag with ' + args.tag + '.') 66 | if totalPeriodEvents != 0: 67 | print('It represents {}% of the events in this period.'.format(round(100 * totalPeriodTags / totalPeriodEvents, 3))) 68 | else: 69 | print ('There is no event answering the research criteria') 70 | 71 | -------------------------------------------------------------------------------- /examples/situational_awareness/tags_count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | from datetime import datetime 7 | import argparse 8 | import tools 9 | import date_tools 10 | 11 | 12 | def init(url, key): 13 | return PyMISP(url, key, misp_verifycert, 'json') 14 | 15 | # ######### fetch data ########## 16 | 17 | 18 | if __name__ == '__main__': 19 | parser = argparse.ArgumentParser(description='Take a sample of events (based on last.py) and give the repartition of tags in this sample.') 20 | parser.add_argument("-d", "--days", type=int, help="number of days before today to search. If not define, default value is 7") 21 | parser.add_argument("-b", "--begindate", default='1970-01-01', help="The research will look for tags attached to events posted at or after the given startdate (format: yyyy-mm-dd): If no date is given, default time is epoch time (1970-1-1)") 22 | parser.add_argument("-e", "--enddate", help="The research will look for tags attached to events posted at or before the given enddate (format: yyyy-mm-dd): If no date is given, default time is now()") 23 | 24 | args = parser.parse_args() 25 | 26 | misp = init(misp_url, misp_key) 27 | 28 | if args.days is None: 29 | args.days = 7 30 | result = misp.search(last='{}d'.format(args.days), metadata=True) 31 | 32 | date_tools.checkDateConsistancy(args.begindate, args.enddate, date_tools.getLastdate(args.days)) 33 | 34 | if args.begindate is None: 35 | args.begindate = date_tools.getLastdate(args.days) 36 | else: 37 | args.begindate = date_tools.setBegindate(date_tools.toDatetime(args.begindate), date_tools.getLastdate(args.days)) 38 | 39 | if args.enddate is None: 40 | args.enddate = datetime.now() 41 | else: 42 | args.enddate = date_tools.setEnddate(date_tools.toDatetime(args.enddate)) 43 | 44 | if 'response' in result: 45 | events = tools.selectInRange(tools.eventsListBuildFromArray(result), begin=args.begindate, end=args.enddate) 46 | tags = tools.tagsListBuild(events) 47 | result = tools.getNbOccurenceTags(tags) 48 | else: 49 | result = 'There is no event during the studied period' 50 | 51 | text = 'Studied pediod: from ' 52 | if args.begindate is None: 53 | text = text + '1970-01-01' 54 | else: 55 | text = text + str(args.begindate.date()) 56 | text = text + ' to ' 57 | if args.enddate is None: 58 | text = text + str(datetime.now().date()) 59 | else: 60 | text = text + str(args.enddate.date()) 61 | 62 | print('\n========================================================') 63 | print(text) 64 | print(result) 65 | -------------------------------------------------------------------------------- /examples/situational_awareness/test_attribute_treemap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/suricata_search/README.md: -------------------------------------------------------------------------------- 1 | This script was outdated and didn't work on the current version of PyMISP. 2 | 3 | For reference, you can look at this repository: https://github.com/raw-data/pymisp-suricata_search 4 | -------------------------------------------------------------------------------- /examples/tags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | import json 8 | 9 | 10 | def get_tags(m): 11 | result = m.get_all_tags(True) 12 | r = result 13 | print(json.dumps(r) + '\n') 14 | 15 | 16 | if __name__ == '__main__': 17 | parser = argparse.ArgumentParser(description='Get tags from MISP instance.') 18 | 19 | args = parser.parse_args() 20 | 21 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 22 | 23 | tags = misp.tags(pythonify=True) 24 | for tag in tags: 25 | print(tag.to_json()) 26 | -------------------------------------------------------------------------------- /examples/test_sign.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | 6 | from pymisp import mispevent 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Sign & verify a MISP event.') 11 | parser.add_argument("-i", "--input", required=True, help="Json file") 12 | parser.add_argument("-u", "--uid", required=True, help="GPG UID") 13 | args = parser.parse_args() 14 | 15 | me = mispevent.MISPEvent() 16 | me.load(args.input) 17 | 18 | me.sign(args.uid) 19 | me.verify(args.uid) 20 | -------------------------------------------------------------------------------- /examples/trustar.conf: -------------------------------------------------------------------------------- 1 | [trustar] 2 | 3 | # endpoint that provides oauth token 4 | auth_endpoint = https://api.trustar.co/oauth/token 5 | 6 | # base API URL access endpoint 7 | api_endpoint = https://api.trustar.co/api/1.3 8 | 9 | # Generate and copy your API key and secret on user API settings page on Station: https://station.trustar.co/settings/api 10 | user_api_key = '#{API_KEY}' 11 | user_api_secret = '#{API_SECRET}' 12 | 13 | # OPTIONAL: enter one or more comma-separate enclave IDs to submit to - get these from API settings page on Station 14 | # enclave_ids = abcdef,1234f 15 | -------------------------------------------------------------------------------- /examples/trustar_misp.py: -------------------------------------------------------------------------------- 1 | from trustar import TruStar, datetime_to_millis 2 | from datetime import datetime, timedelta 3 | from keys import misp_url, misp_key, misp_verifycert 4 | from pymisp import PyMISP, MISPEvent, MISPOrganisation, MISPObject 5 | 6 | # enclave_ids = '7a33144f-aef3-442b-87d4-dbf70d8afdb0' # RHISAC 7 | enclave_ids = None 8 | 9 | time_interval = {'days': 30, 'hours': 0} 10 | 11 | distribution = None # Optional, defaults to MISP.default_event_distribution in MISP config 12 | threat_level_id = None # Optional, defaults to MISP.default_event_threat_level in MISP config 13 | analysis = None # Optional, defaults to 0 (initial analysis) 14 | 15 | 16 | 17 | tru = TruStar() 18 | 19 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 20 | 21 | now = datetime.now() 22 | 23 | # date range for pulling reports is last 4 hours when script is run 24 | to_time = datetime.now() 25 | from_time = to_time - timedelta(**time_interval) 26 | 27 | # convert to millis since epoch 28 | to_time = datetime_to_millis(to_time) 29 | from_time = datetime_to_millis(from_time) 30 | 31 | if not enclave_ids: 32 | reports = tru.get_reports(from_time=from_time, 33 | to_time=to_time) 34 | else: 35 | reports = tru.get_reports(from_time=from_time, 36 | to_time=to_time, 37 | is_enclave=True, 38 | enclave_ids=enclave_ids) 39 | 40 | # loop through each trustar report and create MISP events for each 41 | for report in reports: 42 | # initialize and set MISPEvent() 43 | event = MISPEvent() 44 | event.info = report.title 45 | event.distribution = distribution 46 | event.threat_level_id = threat_level_id 47 | event.analysis = analysis 48 | 49 | # get tags for report 50 | for tag in tru.get_enclave_tags(report.id): 51 | event.add_tag(tag.name) 52 | 53 | obj = MISPObject('trustar_report', standalone=False, strict=True) 54 | # get indicators for report 55 | for indicator in tru.get_indicators_for_report(report.id): 56 | obj.add_attribute(indicator.type, indicator.value) 57 | event.add_object(obj) 58 | # post each event to MISP via API 59 | misp.add_event(event) 60 | -------------------------------------------------------------------------------- /examples/up.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP, MISPEvent 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description="Update a MISP event.") 11 | parser.add_argument("-e", "--event", required=True, help="Event ID to update.") 12 | parser.add_argument("-i", "--input", required=True, help="Input file") 13 | 14 | args = parser.parse_args() 15 | 16 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 17 | 18 | me = MISPEvent() 19 | me.load_file(args.input) 20 | 21 | result = misp.update_event(me, args.event) 22 | -------------------------------------------------------------------------------- /examples/upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP, MISPEvent, MISPAttribute 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | from pathlib import Path 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Send malware sample to MISP.') 11 | parser.add_argument("-u", "--upload", type=str, required=True, help="File or directory of files to upload.") 12 | parser.add_argument("-d", "--distrib", type=int, help="The distribution setting used for the attributes and for the newly created event, if relevant. [0-3].") 13 | parser.add_argument("-c", "--comment", type=str, help="Comment for the uploaded file(s).") 14 | parser.add_argument('-m', '--is-malware', action='store_true', help='The file(s) to upload are malwares') 15 | parser.add_argument('--expand', action='store_true', help='(Only if the file is a malware) Run lief expansion (creates objects)') 16 | parser.add_argument("-e", "--event", type=int, default=None, help="Not supplying an event ID will cause MISP to create a single new event for all of the POSTed malware samples.") 17 | parser.add_argument("-i", "--info", help="Used to populate the event info field if no event ID supplied.") 18 | args = parser.parse_args() 19 | 20 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 21 | 22 | files = [] 23 | p = Path(args.upload) 24 | if p.is_file(): 25 | files = [p] 26 | elif p.is_dir(): 27 | files = [f for f in p.glob('**/*') if f.is_file()] 28 | else: 29 | print('invalid upload path (must be file or dir)') 30 | exit(0) 31 | 32 | if args.is_malware: 33 | arg_type = 'malware-sample' 34 | else: 35 | arg_type = 'attachment' 36 | 37 | # Create attributes 38 | attributes = [] 39 | for f in files: 40 | a = MISPAttribute() 41 | a.type = arg_type 42 | a.value = f.name 43 | a.data = f 44 | a.comment = args.comment 45 | a.distribution = args.distrib 46 | if args.expand and arg_type == 'malware-sample': 47 | a.expand = 'binary' 48 | attributes.append(a) 49 | 50 | if args.event: 51 | for a in attributes: 52 | misp.add_attribute(args.event, a) 53 | else: 54 | m = MISPEvent() 55 | m.info = args.info 56 | m.distribution = args.distrib 57 | m.attributes = attributes 58 | if args.expand and arg_type == 'malware-sample': 59 | m.run_expansions() 60 | misp.add_event(m) 61 | -------------------------------------------------------------------------------- /examples/user_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "email":"maaiil@domain.lu", 3 | "org_id":1, 4 | "role_id":1, 5 | "autoalert":1 6 | } 7 | -------------------------------------------------------------------------------- /examples/users_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key, misp_verifycert 6 | import argparse 7 | 8 | 9 | if __name__ == '__main__': 10 | parser = argparse.ArgumentParser(description='Get a list of the sharing groups from the MISP instance.') 11 | 12 | misp = PyMISP(misp_url, misp_key, misp_verifycert) 13 | 14 | users_list = misp.users(pythonify=True) 15 | print(users_list) 16 | -------------------------------------------------------------------------------- /examples/warninglists.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from pymisp.tools import load_warninglists 6 | import argparse 7 | from keys import misp_url, misp_key, misp_verifycert 8 | 9 | 10 | if __name__ == '__main__': 11 | 12 | parser = argparse.ArgumentParser(description='Load the warninglists.') 13 | parser.add_argument("-p", "--package", action='store_true', help="from the PyMISPWarninglists package.") 14 | parser.add_argument("-r", "--remote", action='store_true', help="from the MISP instance.") 15 | 16 | args = parser.parse_args() 17 | 18 | if args.package: 19 | print(load_warninglists.from_package()) 20 | elif args.remote: 21 | pm = PyMISP(misp_url, misp_key, misp_verifycert) 22 | print(load_warninglists.from_instance(pm)) 23 | -------------------------------------------------------------------------------- /examples/yara.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pymisp import PyMISP 5 | from keys import misp_url, misp_key,misp_verifycert 6 | import argparse 7 | import os 8 | 9 | 10 | def init(url, key): 11 | return PyMISP(url, key, misp_verifycert, 'json') 12 | 13 | 14 | def get_yara(m, event_id, out=None): 15 | ok, rules = m.get_yara(event_id) 16 | if not ok: 17 | print(rules) 18 | elif out is None: 19 | print(rules) 20 | else: 21 | with open(out, 'w') as f: 22 | f.write(rules) 23 | 24 | 25 | if __name__ == '__main__': 26 | parser = argparse.ArgumentParser(description='Get yara rules from an event.') 27 | parser.add_argument("-e", "--event", required=True, help="Event ID.") 28 | parser.add_argument("-o", "--output", help="Output file") 29 | 30 | args = parser.parse_args() 31 | 32 | if args.output is not None and os.path.exists(args.output): 33 | print('Output file already exists, abord.') 34 | exit(0) 35 | 36 | misp = init(misp_url, misp_key) 37 | 38 | get_yara(misp, args.event, args.output) 39 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | strict = True 3 | warn_return_any = False 4 | show_error_context = True 5 | pretty = True 6 | exclude = tests/testlive_comprehensive.py|tests/testlive_sync.py|feed-generator|examples|pymisp/data|docs|pymisp/tools/openioc.py|pymisp/tools/reportlab_generator.py|tests/test_reportlab.py 7 | 8 | # Stuff to remove gradually 9 | disallow_untyped_defs = False 10 | disallow_untyped_calls = False 11 | disable_error_code = arg-type,return-value,assignment,call-overload,union-attr 12 | 13 | 14 | [mypy-docs.source.*] 15 | ignore_errors = True 16 | -------------------------------------------------------------------------------- /pymisp/exceptions.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class PyMISPError(Exception): 5 | def __init__(self, message: str) -> None: 6 | super().__init__(message) 7 | self.message = message 8 | 9 | 10 | class NewEventError(PyMISPError): 11 | pass 12 | 13 | 14 | class UpdateEventError(PyMISPError): 15 | pass 16 | 17 | 18 | class NewAttributeError(PyMISPError): 19 | pass 20 | 21 | 22 | class NewEventReportError(PyMISPError): 23 | pass 24 | 25 | 26 | class NewAnalystDataError(PyMISPError): 27 | pass 28 | 29 | 30 | class NewNoteError(PyMISPError): 31 | pass 32 | 33 | 34 | class NewOpinionError(PyMISPError): 35 | pass 36 | 37 | 38 | class NewRelationshipError(PyMISPError): 39 | pass 40 | 41 | 42 | class UpdateAttributeError(PyMISPError): 43 | pass 44 | 45 | 46 | class NewGalaxyClusterError(PyMISPError): 47 | pass 48 | 49 | 50 | class NewGalaxyClusterRelationError(PyMISPError): 51 | pass 52 | 53 | 54 | class SearchError(PyMISPError): 55 | pass 56 | 57 | 58 | class MissingDependency(PyMISPError): 59 | pass 60 | 61 | 62 | class NoURL(PyMISPError): 63 | pass 64 | 65 | 66 | class NoKey(PyMISPError): 67 | pass 68 | 69 | 70 | class MISPAttributeException(PyMISPError): 71 | """A base class for attribute specific exceptions""" 72 | 73 | class MISPObjectException(PyMISPError): 74 | """A base class for object specific exceptions""" 75 | 76 | 77 | class InvalidMISPAttribute(MISPAttributeException): 78 | """Exception raised when an attribute doesn't respect the constraints in the definition""" 79 | 80 | class InvalidMISPObjectAttribute(MISPAttributeException): 81 | """Exception raised when an object attribute doesn't respect the constraints in the definition""" 82 | 83 | class InvalidMISPObject(MISPObjectException): 84 | """Exception raised when an object doesn't respect the constraints in the definition""" 85 | 86 | 87 | class UnknownMISPObjectTemplate(MISPObjectException): 88 | """Exception raised when the template is unknown""" 89 | 90 | 91 | 92 | class InvalidMISPGalaxy(PyMISPError): 93 | pass 94 | 95 | 96 | class PyMISPInvalidFormat(PyMISPError): 97 | pass 98 | 99 | 100 | class MISPServerError(PyMISPError): 101 | pass 102 | 103 | 104 | class PyMISPNotImplementedYet(PyMISPError): 105 | pass 106 | 107 | 108 | class PyMISPUnexpectedResponse(PyMISPError): 109 | pass 110 | 111 | 112 | class PyMISPEmptyResponse(PyMISPError): 113 | pass 114 | -------------------------------------------------------------------------------- /pymisp/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/pymisp/py.typed -------------------------------------------------------------------------------- /pymisp/tools/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .vtreportobject import VTReportObject # noqa 4 | from .neo4j import Neo4j # noqa 5 | from .fileobject import FileObject # noqa 6 | from .create_misp_object import make_binary_objects # noqa 7 | from .abstractgenerator import AbstractMISPObjectGenerator # noqa 8 | from .genericgenerator import GenericObjectGenerator # noqa 9 | from .openioc import load_openioc, load_openioc_file # noqa 10 | from .sbsignatureobject import SBSignatureObject # noqa 11 | from .fail2banobject import Fail2BanObject # noqa 12 | from .domainipobject import DomainIPObject # noqa 13 | from .asnobject import ASNObject # noqa 14 | from .geolocationobject import GeolocationObject # noqa 15 | from .git_vuln_finder_object import GitVulnFinderObject # noqa 16 | 17 | from .vehicleobject import VehicleObject # noqa 18 | from .csvloader import CSVLoader # noqa 19 | from .sshauthkeyobject import SSHAuthorizedKeysObject # noqa 20 | from .feed import feed_meta_generator # noqa 21 | from .update_objects import update_objects # noqa 22 | 23 | try: 24 | from .emailobject import EMailObject # noqa 25 | except ImportError: 26 | # Requires 'extract_msg', "RTFDE", "oletools" 27 | # pymisp needs to be installed with the email parameter 28 | pass 29 | 30 | try: 31 | from .urlobject import URLObject # noqa 32 | except ImportError: 33 | # Requires pyfaup, optional dependency [url] 34 | pass 35 | except OSError: 36 | # faup required liblua-5.3 37 | pass 38 | 39 | try: 40 | from .peobject import PEObject, PESectionObject # noqa 41 | from .elfobject import ELFObject, ELFSectionObject # noqa 42 | from .machoobject import MachOObject, MachOSectionObject # noqa 43 | except ImportError: 44 | # Requires lief, optional [fileobjects] 45 | pass 46 | 47 | __all__ = ['VTReportObject', 'Neo4j', 'FileObject', 'make_binary_objects', 48 | 'AbstractMISPObjectGenerator', 'GenericObjectGenerator', 49 | 'load_openioc', 'load_openioc_file', 'SBSignatureObject', 50 | 'Fail2BanObject', 'DomainIPObject', 'ASNObject', 'GeolocationObject', 51 | 'GitVulnFinderObject', 'VehicleObject', 'CSVLoader', 52 | 'SSHAuthorizedKeysObject', 'feed_meta_generator', 'update_objects', 53 | 'EMailObject', 'URLObject', 'PEObject', 'PESectionObject', 'ELFObject', 54 | 'ELFSectionObject', 'MachOObject', 'MachOSectionObject' 55 | ] 56 | -------------------------------------------------------------------------------- /pymisp/tools/abstractgenerator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | from datetime import datetime, date 6 | from dateutil.parser import parse 7 | 8 | from typing import Any 9 | 10 | from .. import MISPObject 11 | from ..exceptions import InvalidMISPObject 12 | 13 | 14 | class AbstractMISPObjectGenerator(MISPObject): 15 | 16 | def _detect_epoch(self, timestamp: str | int | float) -> bool: 17 | try: 18 | tmp = float(timestamp) 19 | if tmp < 30000000: 20 | # Assuming the user doesn't want to report anything before datetime(1970, 12, 14, 6, 20) 21 | # The date is most probably in the format 20180301 22 | return False 23 | return True 24 | except ValueError: 25 | return False 26 | 27 | def _sanitize_timestamp(self, timestamp: datetime | date | dict[str, Any] | str | int | float | None = None) -> datetime: 28 | if not timestamp: 29 | return datetime.now() 30 | 31 | if isinstance(timestamp, datetime): 32 | return timestamp 33 | elif isinstance(timestamp, date): 34 | return datetime.combine(timestamp, datetime.min.time()) 35 | elif isinstance(timestamp, dict): 36 | if not isinstance(timestamp['value'], datetime): 37 | timestamp['value'] = parse(timestamp['value']) 38 | return timestamp['value'] 39 | else: # Supported: float/int/string 40 | if isinstance(timestamp, (str, int, float)) and self._detect_epoch(timestamp): 41 | # It converts to the *local* datetime, which is consistent with the rest of the code. 42 | return datetime.fromtimestamp(float(timestamp)) 43 | elif isinstance(timestamp, str): 44 | return parse(timestamp) 45 | else: 46 | raise Exception(f'Unable to convert {timestamp} to a datetime.') 47 | 48 | def generate_attributes(self) -> None: 49 | """Contains the logic where all the values of the object are gathered""" 50 | if hasattr(self, '_parameters') and self._definition is not None: 51 | for object_relation in self._definition['attributes']: 52 | value = self._parameters.pop(object_relation, None) 53 | if not value: 54 | continue 55 | if isinstance(value, dict): 56 | self.add_attribute(object_relation, **value) 57 | elif isinstance(value, list): 58 | self.add_attributes(object_relation, *value) 59 | else: 60 | # Assume it is the value only 61 | self.add_attribute(object_relation, value=value) 62 | if self._strict and self._known_template and self._parameters: 63 | raise InvalidMISPObject('Some object relations are unknown in the template and could not be attached: {}'.format(', '.join(self._parameters))) 64 | -------------------------------------------------------------------------------- /pymisp/tools/asnobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from typing import Any 8 | 9 | from .abstractgenerator import AbstractMISPObjectGenerator 10 | 11 | logger = logging.getLogger('pymisp') 12 | 13 | 14 | class ASNObject(AbstractMISPObjectGenerator): 15 | 16 | def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def] 17 | super().__init__('asn', strict=strict, **kwargs) 18 | self._parameters = parameters 19 | self.generate_attributes() 20 | 21 | def generate_attributes(self) -> None: 22 | first = self._sanitize_timestamp(self._parameters.pop('first-seen', None)) 23 | self._parameters['first-seen'] = first 24 | last = self._sanitize_timestamp(self._parameters.pop('last-seen', None)) 25 | self._parameters['last-seen'] = last 26 | super().generate_attributes() 27 | -------------------------------------------------------------------------------- /pymisp/tools/create_misp_object.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from io import BytesIO 8 | from typing import Any, TYPE_CHECKING 9 | 10 | from ..exceptions import MISPObjectException 11 | from . import FileObject 12 | logger = logging.getLogger('pymisp') 13 | 14 | try: 15 | import lief 16 | import lief.logging 17 | lief.logging.disable() 18 | HAS_LIEF = True 19 | 20 | from .peobject import make_pe_objects 21 | from .elfobject import make_elf_objects 22 | from .machoobject import make_macho_objects 23 | except AttributeError: 24 | HAS_LIEF = False 25 | logger.critical('You need lief >= 0.11.0. The quick and dirty fix is: pip3 install --force pymisp[fileobjects]') 26 | 27 | except ImportError: 28 | HAS_LIEF = False 29 | 30 | if TYPE_CHECKING: 31 | from . import PEObject, ELFObject, MachOObject, PESectionObject, ELFSectionObject, MachOSectionObject 32 | 33 | 34 | class FileTypeNotImplemented(MISPObjectException): 35 | pass 36 | 37 | 38 | def make_binary_objects(filepath: str | None = None, 39 | pseudofile: BytesIO | bytes | None = None, 40 | filename: str | None = None, 41 | standalone: bool = True, 42 | default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, PEObject | ELFObject | MachOObject | None, list[PESectionObject] | list[ELFSectionObject] | list[MachOSectionObject]]: 43 | misp_file = FileObject(filepath=filepath, pseudofile=pseudofile, filename=filename, 44 | standalone=standalone, default_attributes_parameters=default_attributes_parameters) 45 | if HAS_LIEF and (filepath or pseudofile): 46 | if filepath: 47 | lief_parsed = lief.parse(filepath=filepath) 48 | elif pseudofile: 49 | if isinstance(pseudofile, bytes): 50 | lief_parsed = lief.parse(raw=pseudofile) 51 | else: # BytesIO 52 | lief_parsed = lief.parse(obj=pseudofile) 53 | else: 54 | logger.critical('You need either a filepath, or a pseudofile and a filename.') 55 | lief_parsed = None 56 | 57 | if isinstance(lief_parsed, lief.lief_errors): 58 | logger.warning('Got an error parsing the file: {lief_parsed}') 59 | elif isinstance(lief_parsed, lief.PE.Binary): 60 | return make_pe_objects(lief_parsed, misp_file, standalone, default_attributes_parameters) 61 | elif isinstance(lief_parsed, lief.ELF.Binary): 62 | return make_elf_objects(lief_parsed, misp_file, standalone, default_attributes_parameters) 63 | elif isinstance(lief_parsed, lief.MachO.Binary): 64 | return make_macho_objects(lief_parsed, misp_file, standalone, default_attributes_parameters) 65 | else: 66 | logger.critical(f'Unexpected type from lief: {type(lief_parsed)}') 67 | if not HAS_LIEF: 68 | logger.warning('Please install lief, documentation here: https://github.com/lief-project/LIEF') 69 | return misp_file, None, [] 70 | -------------------------------------------------------------------------------- /pymisp/tools/csvloader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import annotations 4 | 5 | from pathlib import Path 6 | 7 | import csv 8 | from pymisp import MISPObject 9 | 10 | 11 | class CSVLoader(): 12 | 13 | def __init__(self, template_name: str, csv_path: Path, 14 | fieldnames: list[str] | None = None, has_fieldnames: bool=False, 15 | delimiter: str = ',', quotechar: str = '"') -> None: 16 | self.template_name = template_name 17 | self.delimiter = delimiter 18 | self.quotechar = quotechar 19 | self.csv_path = csv_path 20 | self.fieldnames = [] 21 | if fieldnames: 22 | self.fieldnames = [f.strip() for f in fieldnames] 23 | if not self.fieldnames: 24 | # If the user doesn't pass fieldnames, they must be in the CSV. 25 | self.has_fieldnames = True 26 | else: 27 | self.has_fieldnames = has_fieldnames 28 | 29 | def load(self) -> list[MISPObject]: 30 | 31 | objects = [] 32 | 33 | with open(self.csv_path, newline='') as csvfile: 34 | reader = csv.reader(csvfile, delimiter=self.delimiter, quotechar=self.quotechar) 35 | if self.has_fieldnames: 36 | # The file has fieldnames, we either ignore it, or use them as object-relation 37 | fieldnames = [f.strip() for f in reader.__next__()] 38 | if not self.fieldnames: 39 | self.fieldnames = fieldnames 40 | 41 | if not self.fieldnames: 42 | raise Exception('No fieldnames, impossible to create objects.') 43 | 44 | # Check if the CSV file has a header, and if it matches with the object template 45 | tmp_object = MISPObject(self.template_name) 46 | 47 | if not tmp_object._definition or not tmp_object._definition['attributes']: 48 | raise Exception(f'Unable to find the object template ({self.template_name}), impossible to create objects.') 49 | allowed_fieldnames = list(tmp_object._definition['attributes'].keys()) 50 | for fieldname in self.fieldnames: 51 | if fieldname not in allowed_fieldnames: 52 | raise Exception(f'{fieldname} is not a valid object relation for {self.template_name}: {allowed_fieldnames}') 53 | 54 | for row in reader: 55 | tmp_object = MISPObject(self.template_name) 56 | has_attribute = False 57 | for object_relation, value in zip(self.fieldnames, row): 58 | if value: 59 | has_attribute = True 60 | tmp_object.add_attribute(object_relation, value=value) 61 | if has_attribute: 62 | objects.append(tmp_object) 63 | return objects 64 | -------------------------------------------------------------------------------- /pymisp/tools/domainipobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from typing import Any 8 | 9 | from .abstractgenerator import AbstractMISPObjectGenerator 10 | 11 | logger = logging.getLogger('pymisp') 12 | 13 | 14 | class DomainIPObject(AbstractMISPObjectGenerator): 15 | 16 | def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def] 17 | super().__init__('domain-ip', strict=strict, **kwargs) 18 | self._parameters = parameters 19 | self.generate_attributes() 20 | 21 | def generate_attributes(self) -> None: 22 | first = self._sanitize_timestamp(self._parameters.pop('first-seen', None)) 23 | self._parameters['first-seen'] = first 24 | last = self._sanitize_timestamp(self._parameters.pop('last-seen', None)) 25 | self._parameters['last-seen'] = last 26 | super().generate_attributes() 27 | -------------------------------------------------------------------------------- /pymisp/tools/ext_lookups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | try: 6 | from pymispgalaxies import Clusters # type: ignore 7 | has_pymispgalaxies = True 8 | except ImportError: 9 | has_pymispgalaxies = False 10 | 11 | try: 12 | from pytaxonomies import Taxonomies # type: ignore 13 | has_pymispgalaxies = True 14 | except ImportError: 15 | has_pymispgalaxies = False 16 | 17 | 18 | def revert_tag_from_galaxies(tag: str) -> list[str]: 19 | clusters = Clusters() 20 | try: 21 | return clusters.revert_machinetag(tag) 22 | except Exception: 23 | return [] 24 | 25 | 26 | def revert_tag_from_taxonomies(tag: str) -> list[str]: 27 | taxonomies = Taxonomies() 28 | try: 29 | return taxonomies.revert_machinetag(tag) 30 | except Exception: 31 | return [] 32 | 33 | 34 | def search_taxonomies(query: str) -> list[str]: 35 | taxonomies = Taxonomies() 36 | found = taxonomies.search(query) 37 | if not found: 38 | found = taxonomies.search(query, expanded=True) 39 | return found 40 | 41 | 42 | def search_galaxies(query: str) -> list[str]: 43 | clusters = Clusters() 44 | return clusters.search(query) 45 | -------------------------------------------------------------------------------- /pymisp/tools/fail2banobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from typing import Any 8 | 9 | from .abstractgenerator import AbstractMISPObjectGenerator 10 | 11 | logger = logging.getLogger('pymisp') 12 | 13 | 14 | class Fail2BanObject(AbstractMISPObjectGenerator): 15 | 16 | def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs): # type: ignore[no-untyped-def] 17 | super().__init__('fail2ban', strict=strict, **kwargs) 18 | self._parameters = parameters 19 | self.generate_attributes() 20 | 21 | def generate_attributes(self) -> None: 22 | timestamp = self._sanitize_timestamp(self._parameters.pop('processing-timestamp', None)) 23 | self._parameters['processing-timestamp'] = timestamp 24 | super().generate_attributes() 25 | -------------------------------------------------------------------------------- /pymisp/tools/feed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import annotations 4 | 5 | from pathlib import Path 6 | from pymisp import MISPEvent 7 | import json 8 | 9 | 10 | def feed_meta_generator(path: Path) -> None: 11 | manifests = {} 12 | hashes: list[str] = [] 13 | 14 | for f_name in path.glob('*.json'): 15 | if str(f_name.name) == 'manifest.json': 16 | continue 17 | event = MISPEvent() 18 | event.load_file(str(f_name)) 19 | manifests.update(event.manifest) 20 | hashes += [f'{h},{event.uuid}' for h in event.attributes_hashes('md5')] 21 | 22 | with (path / 'manifest.json').open('w') as f: 23 | json.dump(manifests, f) 24 | 25 | with (path / 'hashes.csv').open('w') as f: 26 | for h in hashes: 27 | f.write(f'{h}\n') 28 | -------------------------------------------------------------------------------- /pymisp/tools/genericgenerator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | from typing import Any 6 | 7 | from .abstractgenerator import AbstractMISPObjectGenerator 8 | 9 | 10 | class GenericObjectGenerator(AbstractMISPObjectGenerator): 11 | 12 | # FIXME: this method is different from the master one, and that's probably not a good idea. 13 | def generate_attributes(self, attributes: list[dict[str, Any]]) -> None: # type: ignore[override] 14 | """Generates MISPObjectAttributes from a list of dictionaries. 15 | Each entry if the list must be in one of the two following formats: 16 | * {: } 17 | * {: {'value'=, 'type'=, ]} 18 | 19 | Note: Any missing parameter will default to the pre-defined value from the Object template. 20 | If the object template isn't known by PyMISP, you *must* pass a type key/value, or it will fail. 21 | 22 | Example: 23 | [{'analysis_submitted_at': '2018-06-15T06:40:27'}, 24 | {'threat_score': {value=95, to_ids=False}}, 25 | {'permalink': 'https://panacea.threatgrid.com/mask/samples/2e445ef5389d8b'}, 26 | {'heuristic_raw_score': 7.8385159793597}, {'heuristic_score': 96}, 27 | {'original_filename': 'juice.exe'}, {'id': '2e445ef5389d8b'}] 28 | """ 29 | for attribute in attributes: 30 | for object_relation, value in attribute.items(): 31 | if isinstance(value, dict): 32 | self.add_attribute(object_relation, **value) 33 | else: 34 | # In this case, we need a valid template, as all the other parameters will be pre-set. 35 | self.add_attribute(object_relation, value=value) 36 | -------------------------------------------------------------------------------- /pymisp/tools/geolocationobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from typing import Any 8 | 9 | from .abstractgenerator import AbstractMISPObjectGenerator 10 | 11 | logger = logging.getLogger('pymisp') 12 | 13 | 14 | class GeolocationObject(AbstractMISPObjectGenerator): 15 | 16 | def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def] 17 | super().__init__('geolocation', strict=strict, **kwargs) 18 | self._parameters = parameters 19 | self.generate_attributes() 20 | 21 | def generate_attributes(self) -> None: 22 | first = self._sanitize_timestamp(self._parameters.pop('first-seen', None)) 23 | self._parameters['first-seen'] = first 24 | last = self._sanitize_timestamp(self._parameters.pop('last-seen', None)) 25 | self._parameters['last-seen'] = last 26 | super().generate_attributes() 27 | -------------------------------------------------------------------------------- /pymisp/tools/git_vuln_finder_object.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from typing import Any 8 | 9 | from .abstractgenerator import AbstractMISPObjectGenerator 10 | 11 | logger = logging.getLogger('pymisp') 12 | 13 | 14 | class GitVulnFinderObject(AbstractMISPObjectGenerator): 15 | 16 | def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def] 17 | super().__init__('git-vuln-finder', strict=strict, **kwargs) 18 | self._parameters = parameters 19 | self.generate_attributes() 20 | 21 | def generate_attributes(self) -> None: 22 | authored_date = self._sanitize_timestamp(self._parameters.pop('authored_date', None)) 23 | self._parameters['authored_date'] = authored_date 24 | committed_date = self._sanitize_timestamp(self._parameters.pop('committed_date', None)) 25 | self._parameters['committed_date'] = committed_date 26 | if 'stats' in self._parameters: 27 | stats = self._parameters.pop('stats') 28 | self._parameters['stats.insertions'] = stats.pop('insertions') 29 | self._parameters['stats.deletions'] = stats.pop('deletions') 30 | self._parameters['stats.lines'] = stats.pop('lines') 31 | self._parameters['stats.files'] = stats.pop('files') 32 | super().generate_attributes() 33 | -------------------------------------------------------------------------------- /pymisp/tools/load_warninglists.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | from ..api import PyMISP 6 | 7 | try: 8 | from pymispwarninglists import WarningLists, WarningList # type: ignore 9 | has_pymispwarninglists = True 10 | except ImportError: 11 | has_pymispwarninglists = False 12 | 13 | 14 | def from_instance(pymisp_instance: PyMISP, slow_search: bool=False) -> WarningLists: 15 | """Load the warnindlist from an existing MISP instance 16 | :pymisp_instance: Already instantialized PyMISP instance.""" 17 | 18 | warninglists_index = pymisp_instance.warninglists(pythonify=True) 19 | all_warningslists = [] 20 | for warninglist in warninglists_index: 21 | if isinstance(warninglist, WarningList): 22 | wl = pymisp_instance.get_warninglist(warninglist['Warninglist']['id'])['Warninglist'] 23 | wl['list'] = wl.pop('WarninglistEntry') 24 | all_warningslists.append(wl) 25 | 26 | return WarningLists(slow_search, all_warningslists) 27 | 28 | 29 | def from_package(slow_search: bool=False) -> WarningLists: 30 | return WarningLists(slow_search) 31 | -------------------------------------------------------------------------------- /pymisp/tools/neo4j.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import glob 4 | import os 5 | 6 | from .. import MISPEvent 7 | 8 | try: 9 | from py2neo import authenticate, Graph, Node, Relationship # type: ignore 10 | has_py2neo = True 11 | except ImportError: 12 | has_py2neo = False 13 | 14 | 15 | class Neo4j(): 16 | 17 | def __init__(self, host: str='localhost:7474', username: str='neo4j', password: str='neo4j') -> None: 18 | if not has_py2neo: 19 | raise Exception('py2neo is required, please install: pip install py2neo') 20 | authenticate(host, username, password) 21 | self.graph = Graph(f"http://{host}/db/data/") 22 | 23 | def load_events_directory(self, directory: str) -> None: 24 | self.events: list[MISPEvent] = [] 25 | for path in glob.glob(os.path.join(directory, '*.json')): 26 | e = MISPEvent() 27 | e.load(path) 28 | self.import_event(e) 29 | 30 | def del_all(self) -> None: 31 | self.graph.delete_all() 32 | 33 | def import_event(self, event: MISPEvent) -> None: 34 | tx = self.graph.begin() 35 | event_node = Node('Event', uuid=event.uuid, name=event.info) 36 | # event_node['distribution'] = event.distribution 37 | # event_node['threat_level_id'] = event.threat_level_id 38 | # event_node['analysis'] = event.analysis 39 | # event_node['published'] = event.published 40 | # event_node['date'] = event.date.isoformat() 41 | tx.create(event_node) 42 | for a in event.attributes: 43 | attr_node = Node('Attribute', a.type, uuid=a.uuid) 44 | attr_node['category'] = a.category 45 | attr_node['name'] = a.value 46 | # attr_node['to_ids'] = a.to_ids 47 | # attr_node['comment'] = a.comment 48 | # attr_node['distribution'] = a.distribution 49 | tx.create(attr_node) 50 | member_rel = Relationship(event_node, "is member", attr_node) 51 | tx.create(member_rel) 52 | val = Node('Value', name=a.value) 53 | ev = Relationship(event_node, "has", val) 54 | av = Relationship(attr_node, "is", val) 55 | s = val | ev | av 56 | tx.merge(s) 57 | # tx.graph.push(s) 58 | tx.commit() 59 | -------------------------------------------------------------------------------- /pymisp/tools/sbsignatureobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import annotations 4 | 5 | from .abstractgenerator import AbstractMISPObjectGenerator 6 | 7 | 8 | class SBSignatureObject(AbstractMISPObjectGenerator): 9 | ''' 10 | Sandbox Analyzer 11 | ''' 12 | def __init__(self, software: str, report: list[tuple[str, str]], **kwargs) -> None: # type: ignore[no-untyped-def] 13 | super().__init__('sb-signature', **kwargs) 14 | self._software = software 15 | self._report = report 16 | self.generate_attributes() 17 | 18 | def generate_attributes(self) -> None: 19 | ''' Parse the report for relevant attributes ''' 20 | self.add_attribute("software", value=self._software) 21 | for (signature_name, description) in self._report: 22 | self.add_attribute("signature", value=signature_name, comment=description) 23 | -------------------------------------------------------------------------------- /pymisp/tools/sshauthkeyobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from io import StringIO 8 | from pathlib import Path 9 | 10 | from ..exceptions import InvalidMISPObject 11 | from .abstractgenerator import AbstractMISPObjectGenerator 12 | 13 | logger = logging.getLogger('pymisp') 14 | 15 | 16 | class SSHAuthorizedKeysObject(AbstractMISPObjectGenerator): 17 | 18 | def __init__(self, authorized_keys_path: Path | str | None = None, # type: ignore[no-untyped-def] 19 | authorized_keys_pseudofile: StringIO | None = None, **kwargs): 20 | super().__init__('ssh-authorized-keys', **kwargs) 21 | if authorized_keys_path: 22 | with open(authorized_keys_path) as f: 23 | self.__pseudofile = StringIO(f.read()) 24 | elif authorized_keys_pseudofile and isinstance(authorized_keys_pseudofile, StringIO): 25 | self.__pseudofile = authorized_keys_pseudofile 26 | else: 27 | raise InvalidMISPObject('File buffer (StringIO) or a path is required.') 28 | self.__data = self.__pseudofile.getvalue() 29 | self.generate_attributes() 30 | 31 | def generate_attributes(self) -> None: 32 | for line in self.__pseudofile: 33 | if line.startswith('ssh') or line.startswith('ecdsa'): 34 | key = line.split(' ')[1] 35 | self.add_attribute('key', key) 36 | -------------------------------------------------------------------------------- /pymisp/tools/update_objects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import annotations 4 | 5 | import zipfile 6 | from io import BytesIO 7 | from pathlib import Path 8 | 9 | import requests 10 | 11 | from ..abstract import resources_path 12 | 13 | static_repo = "https://github.com/MISP/misp-objects/archive/main.zip" 14 | 15 | 16 | def update_objects() -> None: 17 | r = requests.get(static_repo) 18 | 19 | zipped_repo = BytesIO(r.content) 20 | 21 | with zipfile.ZipFile(zipped_repo, 'r') as myzip: 22 | for name in myzip.namelist(): 23 | if not name.endswith('.json'): 24 | continue 25 | name_on_disk = name.replace('misp-objects-main', 'misp-objects') 26 | path = resources_path / Path(name_on_disk) 27 | if not path.parent.exists(): 28 | path.parent.mkdir(parents=True) 29 | with path.open('wb') as f: 30 | f.write(myzip.read(name)) 31 | -------------------------------------------------------------------------------- /pymisp/tools/urlobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | import logging 6 | 7 | from urllib.parse import unquote_plus 8 | 9 | from .abstractgenerator import AbstractMISPObjectGenerator 10 | 11 | try: 12 | from pyfaup.faup import Faup # type: ignore 13 | except (OSError, ImportError): 14 | from ._psl_faup import PSLFaup as Faup 15 | 16 | logger = logging.getLogger('pymisp') 17 | 18 | faup = Faup() 19 | 20 | 21 | class URLObject(AbstractMISPObjectGenerator): 22 | 23 | def __init__(self, url: str, generate_all=False, **kwargs) -> None: # type: ignore[no-untyped-def] 24 | super().__init__('url', **kwargs) 25 | self._generate_all = True if generate_all is True else False 26 | faup.decode(unquote_plus(url)) 27 | self.generate_attributes() 28 | 29 | def generate_attributes(self) -> None: 30 | self.add_attribute('url', value=faup.url.decode()) 31 | if faup.get_host(): 32 | self.add_attribute('host', value=faup.get_host()) 33 | if faup.get_domain(): 34 | self.add_attribute('domain', value=faup.get_domain()) 35 | if self._generate_all: 36 | if hasattr(faup, 'ip_as_host') and faup.ip_as_host: 37 | self.attributes = [attr for attr in self.attributes 38 | if attr.object_relation not in ('host', 'domain')] 39 | self.add_attribute('ip', value=faup.ip_as_host) 40 | if faup.get_credential(): 41 | self.add_attribute('credential', value=faup.get_credential()) 42 | if faup.get_fragment(): 43 | self.add_attribute('fragment', value=faup.get_fragment()) 44 | if faup.get_port(): 45 | self.add_attribute('port', value=faup.get_port()) 46 | if faup.get_query_string(): 47 | self.add_attribute('query_string', value=faup.get_query_string()) 48 | if faup.get_resource_path(): 49 | self.add_attribute('resource_path', value=faup.get_resource_path()) 50 | if faup.get_scheme(): 51 | self.add_attribute('scheme', value=faup.get_scheme()) 52 | if faup.get_tld(): 53 | self.add_attribute('tld', value=faup.get_tld()) 54 | if faup.get_domain_without_tld(): 55 | self.add_attribute('domain_without_tld', value=faup.get_domain_without_tld()) 56 | if faup.get_subdomain(): 57 | self.add_attribute('subdomain', value=faup.get_subdomain()) 58 | if hasattr(faup, 'get_unicode_host') and faup.get_unicode_host() != faup.get_host(): 59 | self.add_attribute('text', value=faup.get_unicode_host()) 60 | -------------------------------------------------------------------------------- /pymisp/tools/vtreportobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from __future__ import annotations 4 | 5 | import re 6 | from typing import Any 7 | 8 | import requests 9 | try: 10 | import validators 11 | has_validators = True 12 | except ImportError: 13 | has_validators = False 14 | 15 | 16 | from .abstractgenerator import AbstractMISPObjectGenerator 17 | from .. import InvalidMISPObject 18 | 19 | 20 | class VTReportObject(AbstractMISPObjectGenerator): 21 | ''' 22 | VirusTotal Report 23 | 24 | :apikey: VirusTotal API key (private works, but only public features are supported right now) 25 | 26 | :indicator: IOC to search VirusTotal for 27 | ''' 28 | def __init__(self, apikey: str, indicator: str, vt_proxies: dict[str, str] | None = None, **kwargs) -> None: # type: ignore[no-untyped-def] 29 | super().__init__('virustotal-report', **kwargs) 30 | indicator = indicator.strip() 31 | self._resource_type = self.__validate_resource(indicator) 32 | if self._resource_type: 33 | self._proxies = vt_proxies 34 | self._report = self.__query_virustotal(apikey, indicator) 35 | self.generate_attributes() 36 | else: 37 | error_msg = f"A valid indicator is required. (One of type url, md5, sha1, sha256). Received '{indicator}' instead" 38 | raise InvalidMISPObject(error_msg) 39 | 40 | def get_report(self) -> dict[str, Any]: 41 | return self._report 42 | 43 | def generate_attributes(self) -> None: 44 | ''' Parse the VirusTotal report for relevant attributes ''' 45 | self.add_attribute("last-submission", value=self._report["scan_date"]) 46 | self.add_attribute("permalink", value=self._report["permalink"]) 47 | ratio = "{}/{}".format(self._report["positives"], self._report["total"]) 48 | self.add_attribute("detection-ratio", value=ratio) 49 | 50 | def __validate_resource(self, ioc: str) -> str | bool: 51 | ''' 52 | Validate the data type of an indicator. 53 | Domains and IP addresses aren't supported because 54 | they don't return the same type of data as the URLs/files do 55 | 56 | :ioc: Indicator to search VirusTotal for 57 | ''' 58 | if not has_validators: 59 | raise Exception('You need to install validators: pip install validators') 60 | if validators.url(ioc): 61 | return "url" 62 | elif re.match(r"\b([a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{64})\b", ioc): 63 | return "file" 64 | return False 65 | 66 | def __query_virustotal(self, apikey: str, resource: str) -> dict[str, Any]: 67 | ''' 68 | Query VirusTotal for information about an indicator 69 | 70 | :apikey: VirusTotal API key 71 | 72 | :resource: Indicator to search in VirusTotal 73 | ''' 74 | url = f"https://www.virustotal.com/vtapi/v2/{self._resource_type}/report" 75 | params = {"apikey": apikey, "resource": resource} 76 | # for now assume we're using a public API key - we'll figure out private keys later 77 | if self._proxies: 78 | report = requests.get(url, params=params, proxies=self._proxies) 79 | else: 80 | report = requests.get(url, params=params) 81 | report_json = report.json() 82 | if report_json["response_code"] == 1: 83 | return report_json 84 | else: 85 | error_msg = "{}: {}".format(resource, report_json["verbose_msg"]) 86 | raise InvalidMISPObject(error_msg) 87 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "pymisp" 3 | version = "2.5.12" 4 | description = "Python API for MISP." 5 | authors = [ 6 | {name="Raphaël Vinot", email="raphael.vinot@circl.lu"} 7 | ] 8 | license = "BSD-2-Clause" 9 | readme = "README.md" 10 | requires-python = ">=3.9.2,<4.0" 11 | 12 | dynamic = [ "classifiers" ] 13 | 14 | dependencies = [ 15 | "requests (>=2.32.3)", 16 | "python-dateutil (>=2.9.0.post0)", 17 | "deprecated (>=1.2.18)" 18 | ] 19 | 20 | [project.urls] 21 | repository = "https://github.com/MISP/PyMISP" 22 | issues = "https://github.com/MISP/PyMISP/issues" 23 | documentation = "https://pymisp.readthedocs.io" 24 | 25 | [project.optional-dependencies] 26 | 27 | fileobjects = [ 28 | "python-magic (>=0.4.27)", 29 | "pydeep2 (>=0.5.1)", 30 | "lief (>=0.16.5)" 31 | ] 32 | 33 | openioc = [ "beautifulsoup4 (>=4.13.4)" ] 34 | 35 | virustotal = [ "validators (>=0.35.0)" ] 36 | 37 | docs = [ 38 | "sphinx-autodoc-typehints (>=3.2.0) ; python_version >= \"3.11\"", 39 | "sphinx (>=8.2.3) ; python_version >= \"3.11\"", 40 | "docutils (>=0.21.2) ; python_version >= \"3.11\"", 41 | "myst-parser (>=4.0.1) ; python_version >= \"3.11\"" 42 | ] 43 | 44 | pdfexport = [ "reportlab (>=4.4.0)" ] 45 | 46 | url = [ "pyfaup (>=1.2)" ] 47 | 48 | email = [ 49 | "extract_msg (>=0.54.1)", 50 | "RTFDE (>=0.1.2.1) ; python_version <= \"3.9\"", 51 | "oletools (>=0.60.2)" 52 | ] 53 | 54 | brotli = [ "urllib3 (>=2.4.0)" ] 55 | 56 | [tool.poetry] 57 | classifiers=[ 58 | 'Development Status :: 5 - Production/Stable', 59 | 'Environment :: Console', 60 | 'Operating System :: POSIX :: Linux', 61 | 'Intended Audience :: Science/Research', 62 | 'Intended Audience :: Telecommunications Industry', 63 | 'Intended Audience :: Information Technology', 64 | 'Topic :: Security', 65 | 'Topic :: Internet' 66 | ] 67 | 68 | exclude = ["pymisp/data"] 69 | include = [ 70 | {path = "CHANGELOG.txt", format = ["sdist"]}, 71 | {path = "LICENSE", format = ["sdist"]}, 72 | {path = "pymisp/data/*.json", format = ["sdist", "wheel"]}, 73 | {path = "pymisp/data/misp-objects/*.json", format = ["sdist", "wheel"]}, 74 | {path = "pymisp/data/misp-objects/objects/*/definition.json", format = ["sdist", "wheel"]}, 75 | {path = "pymisp/data/misp-objects/relationships/definition.json", format = ["sdist", "wheel"]}, 76 | {path = "docs", format = ["sdist"]}, 77 | {path = "examples/*.py", format = ["sdist"]}, 78 | {path = "tests", format = ["sdist"]} 79 | ] 80 | 81 | [tool.poetry.group.dev.dependencies] 82 | requests-mock = "^1.12.1" 83 | mypy = "^1.15.0" 84 | types-requests = "^2.32.0.20250328" 85 | types-python-dateutil = "^2.9.0.20241206" 86 | types-redis = "^4.6.0.20241004" 87 | types-Flask = "^1.1.6" 88 | pytest-cov = "^6.1.1" 89 | 90 | [build-system] 91 | requires = ["poetry-core>=2.0"] 92 | build-backend = "poetry.core.masonry.api" 93 | -------------------------------------------------------------------------------- /tests/57c4445b-c548-4654-af0b-4be3950d210f.json: -------------------------------------------------------------------------------- 1 | {"Event": {"info": "Ransomware - Xorist", "publish_timestamp": "1472548231", "timestamp": "1472541011", "analysis": "2", "Attribute": [{"category": "External analysis", "comment": "Imported via the Freetext Import Tool - Xchecked via VT: b3c4ae251f8094fa15b510051835c657eaef2a6cea46075d3aec964b14a99f68", "uuid": "57c5300c-0560-4146-bfaa-40e802de0b81", "timestamp": "1472540684", "to_ids": false, "value": "https://www.virustotal.com/file/b3c4ae251f8094fa15b510051835c657eaef2a6cea46075d3aec964b14a99f68/analysis/1469554268/", "type": "link"}, {"category": "External analysis", "comment": "", "uuid": "57c5310b-dc34-43cb-8b8e-4846950d210f", "timestamp": "1472541011", "to_ids": false, "value": "http://www.xylibox.com/2011/06/have-fun-with-trojan-ransomwin32xorist.html", "type": "link"}, {"category": "Other", "comment": "", "uuid": "57c444c0-8004-48fa-9c33-8aca950d210f", "timestamp": "1472480448", "to_ids": false, "value": "UPX packed", "type": "comment"}, {"category": "Other", "comment": "", "uuid": "57c44648-96f4-45d4-a8eb-453e950d210f", "timestamp": "1472480840", "to_ids": false, "value": "Key: 85350044dF4AC3518D185678A9414A7F,\r\nEncryption rounds:8,\r\nStart offset: 64,\r\nAlgorithm: TEA", "type": "text"}, {"category": "Payload delivery", "comment": "Imported via the Freetext Import Tool", "uuid": "57c4448a-fb04-457d-87e7-4127950d210f", "timestamp": "1472480394", "to_ids": true, "value": "3Z4wnG9603it23y.exe", "type": "filename"}, {"category": "Payload delivery", "comment": "Imported via the Freetext Import Tool", "uuid": "57c4448b-454c-4d17-90d1-4d2f950d210f", "timestamp": "1472480395", "to_ids": true, "value": "0749bae92ca336a02c83d126e04ec628", "type": "md5"}, {"category": "Payload delivery", "comment": "Imported via the Freetext Import Tool", "uuid": "57c4448a-bef0-4ba7-a071-444e950d210f", "timestamp": "1472480394", "to_ids": true, "value": "77b0c41b7d340b8a3d903f21347bbf06aa766b5b", "type": "sha1"}, {"category": "Payload delivery", "comment": "Imported via the Freetext Import Tool", "uuid": "57c4448b-3fa4-4d65-9ccc-4afa950d210f", "timestamp": "1472480395", "to_ids": true, "value": "b3c4ae251f8094fa15b510051835c657eaef2a6cea46075d3aec964b14a99f68", "type": "sha256"}, {"category": "Persistence mechanism", "comment": "", "uuid": "57c54b0f-27a4-458b-8e63-4455950d210f", "timestamp": "1472547599", "to_ids": true, "value": "Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run|%TEMP%\\3Z4wnG9603it23y.exe", "type": "regkey|value"}], "Tag": [{"colour": "#ffffff", "exportable": true, "name": "tlp:white"}, {"colour": "#3d7a00", "exportable": true, "name": "circl:incident-classification=\"malware\""}, {"colour": "#420053", "exportable": true, "name": "ms-caro-malware:malware-type=\"Ransom\""}, {"colour": "#2c4f00", "exportable": true, "name": "malware_classification:malware-category=\"Ransomware\""}], "published": true, "date": "2016-08-29", "Orgc": {"name": "CIRCL", "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f"}, "threat_level_id": "3", "uuid": "57c4445b-c548-4654-af0b-4be3950d210f"}} -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/__init__.py -------------------------------------------------------------------------------- /tests/csv_testfiles/invalid_fieldnames.csv: -------------------------------------------------------------------------------- 1 | SHA1,fileName,size 2 | 2a030cc6d84d5785f5e84d0f5888a411d4b06d01,soft.exe,45568 3 | 2abae839362edfe52d9ebe282fb61113d22b331f,sttager.exe,20480 4 | 6995a32e0a4d4f6d0c9b2a00a96d69bff4b83ea7,test443.exe,373911 5 | 87b1f17fbb4a1e8eef4cb31c1c0194b1426c868c,veil.exe,345761 6 | afc36916a4df934446681ea28bef6add4decb98a,80_http.exe.exe,411850 7 | f832d94391a8d2d5cf92773e6c912905ec7c40c7,test1.exe,406636 8 | 056823c7891a04b2fec8903eb401ae3291743a54,beca.exe.exe,23808 9 | b7afa7acf1b7ded2c4e3d0884b5cdaa230d9f82e,shell1.exe,24576 10 | 4b50b6b9157026ab408d966ece02d1cef8045f82,starggge.exe,27136 11 | 6042dfd50d33da40e383baec4a7ef7c75bf17481,8_32.exe,24064 12 | -------------------------------------------------------------------------------- /tests/csv_testfiles/valid_fieldnames.csv: -------------------------------------------------------------------------------- 1 | md5, sha1, sha256 2 | 644087ccca16d2a728ef7685a4106f09, eabd6974ac71efd72d9e0688d5a6131f336d169c, 385e31c97e3a07bbb81513f0cd0979e64e6b014943902efd002f57b21eadd41e 3 | 34187a34d0a3c5d63016c26346371b54, ce8209ff9828aa8cb095bd7d1589fc4d394c298c, 5f815b8a8e77731c9ca2b3a07a27f880ef24d54e458d77bdabbbaf2269fe96c3 4 | 871aa15f4d61c85e1284e1be3f99f705, 236eac0b19f91117b27f1b198a4d8490d99ec2e5, b434bccf0a5ff75b27184e661df751466aef69f35fbd7b8b8692302b8b886262 5 | -------------------------------------------------------------------------------- /tests/email_testfiles/mail_1.eml.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/email_testfiles/mail_1.eml.zip -------------------------------------------------------------------------------- /tests/email_testfiles/mail_1.msg.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/email_testfiles/mail_1.msg.zip -------------------------------------------------------------------------------- /tests/email_testfiles/mail_1_headers_only.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: kinney@noth.com 3 | Received: (qmail 11769 invoked from network); 22 Aug 2016 14:23:01 -0000 4 | Received: from smtprelay0207.b.hostedemail.com (HELO smtprelay.b.hostedemail.com) (64.98.42.207) 5 | by smtp.server.net with SMTP; 22 Aug 2016 14:23:01 -0000 6 | Received: from filter.hostedemail.com (10.5.19.248.rfc1918.com [10.5.19.248]) 7 | by smtprelay06.b.hostedemail.com (Postfix) with ESMTP id 2CC378D014 8 | for ; Mon, 22 Aug 2016 14:22:58 +0000 (UTC) 9 | Received: from DM6PR06MB4475.namprd06.prod.outlook.com (2603:10b6:207:3d::31) 10 | by BL0PR06MB4465.namprd06.prod.outlook.com with HTTPS id 12345 via 11 | BL0PR02CA0054.NAMPRD02.PROD.OUTLOOK.COM; Mon, 1 Oct 2018 09:49:22 +0000 12 | Received: from DM3NAM03FT035.eop-NAM03.prod.protection.outlook.com 13 | (2a01:111:f400:7e49::205) by CY4PR0601CA0051.outlook.office365.com 14 | (2603:10b6:910:89::28) with Microsoft SMTP Server (version=TLS1_2, 15 | cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) id 15.20.1185.23 via Frontend 16 | Transport; Mon, 1 Oct 2018 09:49:21 +0000 17 | X-Session-Marker: 6A64617A657940616C6578616E646572736D6974682E636F6D 18 | X-Spam-Summary: 69,4.5,0,,d41d8cd98f00b204,suvorov.s@nalg.ru,:,RULES_HIT:46:150:152:379:553:871:967:989:1000:1254:1260:1263:1313:1381:1516:1517:1520:1575:1594:1605:1676:1699:1730:1747:1764:1777:1792:1823:2044:2197:2199:2393:2525:2560:2563:2682:2685:2827:2859:2911:2933:2937:2939:2942:2945:2947:2951:2954:3022:3867:3872:3890:3934:3936:3938:3941:3944:3947:3950:3953:3956:3959:4425:5007:6001:6261:6506:6678:6747:6748:7281:7398:7688:8599:8824:8957:9009:9025:9388:10004:10848:11604:11638:11639:11783:11914:12043:12185:12445:12517:12519:12740:13026:14149:14381:14658:14659:14687:21080:21221:30054:30055:30065:30066,0,RBL:none,CacheIP:none,Bayesian:0.5,0.5,0.5,Netcheck:none,DomainCache:0,MSF:not bulk,SPF:fn,MSBL:0,DNSBL:none,Custom_rules:0:0:0,LFtime:5,LUA_SUMMARY:none 19 | X-HE-Tag: print38_7083d7fd63e24 20 | X-Filterd-Recvd-Size: 64695 21 | Received: from computer_3436 (unknown [43.230.105.145]) 22 | (Authenticated sender: jdazey@alexandersmith.com) 23 | by omf06.b.hostedemail.com (Postfix) with ESMTPA 24 | for ; Mon, 22 Aug 2016 14:22:52 +0000 (UTC) 25 | From: =?UTF-8?B?0YHQu9GD0LbQsdCwINCk0J3QoSDQlNCw0L3QuNC40Lsg0KHRg9Cy0L7RgNC+0LI=?= 26 | To: kinney@noth.com 27 | Subject: =?UTF-8?B?0L/QuNGB0YzQvNC+INGD0LLQtdC00L7QvC3QtQ==?= 28 | Content-Type: multipart/mixed; boundary="2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7" -------------------------------------------------------------------------------- /tests/email_testfiles/mail_2.eml: -------------------------------------------------------------------------------- 1 | MIME-Version: 1.0 2 | Date: Fri, 18 Dec 2020 21:20:15 -0500 3 | Message-ID: 4 | Subject: multi-encoded-email 5 | From: Test Testerson 6 | To: Try Tryerson 7 | Cc: TryTwo Tryerson 8 | User-agent: mozilla/5.0 (windows nt 5.1; rv:11.0) gecko firefox/11.0 (via ggpht.com googleimageproxy) 9 | X-Mailer: Apple Mail (2.3445.9.1) 10 | Thread-Index: AQHT48oEYuOLpJgzCU+4lba9aw8zCA== 11 | Reply-To: maliciousreply@thebadguysdomainisthisone.com 12 | Content-Type: multipart/alternative; boundary="0000000000009cc2ea05b6c7dd56" 13 | 14 | --0000000000009cc2ea05b6c7dd56 15 | Content-Type: text/plain; charset="UTF-8" 16 | Content-Transfer-Encoding: base64 17 | 18 | 6YCZ5piv5LiA5YCL5paH5pys5a2X56ym5LiyDQrXlteU15Ug157Xl9eo15XXlteqINeY16fXodeY 19 | Lg0KDQpUaGlzIGlzIHlvdXIgdGhpcmQgZW5jb2RpbmcuLi4uIG1heWJlPw0K 20 | --0000000000009cc2ea05b6c7dd56 21 | Content-Type: text/html; charset="UTF-8" 22 | Content-Transfer-Encoding: quoted-printable 23 | 24 |
=E9=80=99=E6=98=AF=E4=B8=80=E5=80=8B=E6=96=87=E6=9C=AC=E5= 25 | =AD=97=E7=AC=A6=E4=B8=B2
=D7=96=D7=94=D7=95 =D7=9E=D7=97=D7=A8=D7=95=D7= 26 | =96=D7=AA =D7=98=D7=A7=D7=A1=D7=98.

<= 29 | div style=3D"font-size:12.8px">This is your third encoding.... maybe?
= 30 |
31 | 32 | --0000000000009cc2ea05b6c7dd56-- -------------------------------------------------------------------------------- /tests/email_testfiles/mail_3.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/email_testfiles/mail_3.msg -------------------------------------------------------------------------------- /tests/email_testfiles/mail_4.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/email_testfiles/mail_4.msg -------------------------------------------------------------------------------- /tests/email_testfiles/mail_5.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/email_testfiles/mail_5.msg -------------------------------------------------------------------------------- /tests/email_testfiles/mail_multiple_to.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: kinney@noth.com 3 | Received: (qmail 11769 invoked from network); 22 Aug 2016 14:23:01 -0000 4 | X-Session-Marker: 6A64617A657940616C6578616E646572736D6974682E636F6D 5 | X-Spam-Summary: 69,4.5,0,,d41d8cd98f00b204,suvorov.s@nalg.ru,:,RULES_HIT:46:150:152:379:553:871:967:989:1000:1254:1260:1263:1313:1381:1516:1517:1520:1575:1594:1605:1676:1699:1730:1747:1764:1777:1792:1823:2044:2197:2199:2393:2525:2560:2563:2682:2685:2827:2859:2911:2933:2937:2939:2942:2945:2947:2951:2954:3022:3867:3872:3890:3934:3936:3938:3941:3944:3947:3950:3953:3956:3959:4425:5007:6001:6261:6506:6678:6747:6748:7281:7398:7688:8599:8824:8957:9009:9025:9388:10004:10848:11604:11638:11639:11783:11914:12043:12185:12445:12517:12519:12740:13026:14149:14381:14658:14659:14687:21080:21221:30054:30055:30065:30066,0,RBL:none,CacheIP:none,Bayesian:0.5,0.5,0.5,Netcheck:none,DomainCache:0,MSF:not bulk,SPF:fn,MSBL:0,DNSBL:none,Custom_rules:0:0:0,LFtime:5,LUA_SUMMARY:none 6 | X-HE-Tag: print38_7083d7fd63e24 7 | X-Filterd-Recvd-Size: 64695 8 | Received: from computer_3436 (unknown [43.230.105.145]) 9 | (Authenticated sender: jdazey@alexandersmith.com) 10 | by omf06.b.hostedemail.com (Postfix) with ESMTPA 11 | for ; Mon, 22 Aug 2016 14:22:52 +0000 (UTC) 12 | From: =?UTF-8?B?0YHQu9GD0LbQsdCwINCk0J3QoSDQlNCw0L3QuNC40Lsg0KHRg9Cy0L7RgNC+0LI=?= 13 | To: "Novak, Jan" , "Marek, Jan" 14 | Subject: =?UTF-8?B?0L/QuNGB0YzQvNC+INGD0LLQtdC00L7QvC3QtQ==?= 15 | Content-Type: multipart/mixed; boundary="2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7" 16 | -------------------------------------------------------------------------------- /tests/email_testfiles/source: -------------------------------------------------------------------------------- 1 | Testing emails come from https://github.com/SpamScope/mail-parser/ -------------------------------------------------------------------------------- /tests/misp_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "Attribute": [ 3 | { 4 | "ShadowAttribute": [], 5 | "category": "Payload delivery", 6 | "comment": "", 7 | "deleted": false, 8 | "distribution": "5", 9 | "event_id": "2", 10 | "id": "7", 11 | "sharing_group_id": "0", 12 | "timestamp": "1465681304", 13 | "to_ids": false, 14 | "type": "url", 15 | "uuid": "575c8598-f1f0-4c16-a94a-0612c0a83866", 16 | "value": "http://fake.website.com/malware/is/here" 17 | }, 18 | { 19 | "ShadowAttribute": [], 20 | "category": "Payload type", 21 | "comment": "", 22 | "deleted": false, 23 | "distribution": "5", 24 | "event_id": "2", 25 | "id": "6", 26 | "sharing_group_id": "0", 27 | "timestamp": "1465681801", 28 | "to_ids": false, 29 | "type": "text", 30 | "uuid": "575c8549-9010-4555-8b37-057ac0a83866", 31 | "value": "Locky" 32 | } 33 | ], 34 | "Org": { 35 | "id": "1", 36 | "name": "ORGNAME", 37 | "uuid": "57586e9a-4a64-4f79-9009-4dc1c0a83866" 38 | }, 39 | "Orgc": { 40 | "id": "1", 41 | "name": "ORGNAME", 42 | "uuid": "57586e9a-4a64-4f79-9009-4dc1c0a83866" 43 | }, 44 | "RelatedEvent": [], 45 | "ShadowAttribute": [], 46 | "Tag": [ 47 | { 48 | "colour": "#005a5a", 49 | "exportable": true, 50 | "id": "6", 51 | "name": "ecsirt:malicious-code=\"ransomware\"" 52 | }, 53 | { 54 | "colour": "#142bf7", 55 | "exportable": true, 56 | "id": "1", 57 | "name": "for_intelmq_processing" 58 | } 59 | ], 60 | "analysis": "0", 61 | "attribute_count": "2", 62 | "date": "2016-06-09", 63 | "distribution": "0", 64 | "id": "2", 65 | "info": "A Random Event", 66 | "locked": false, 67 | "org_id": "1", 68 | "orgc_id": "1", 69 | "proposal_email_lock": false, 70 | "publish_timestamp": "0", 71 | "published": false, 72 | "sharing_group_id": "0", 73 | "threat_level_id": "1", 74 | "timestamp": "1465681801", 75 | "uuid": "5758ebf5-c898-48e6-9fe9-5665c0a83866" 76 | } 77 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/attribute.json: -------------------------------------------------------------------------------- 1 | { 2 | "Attribute": [ 3 | { 4 | "Tag": [ 5 | { 6 | "name": "osint" 7 | } 8 | ], 9 | "category": "Payload delivery", 10 | "disable_correlation": false, 11 | "to_ids": true, 12 | "type": "filename", 13 | "value": "bar.exe" 14 | } 15 | ], 16 | "analysis": "1", 17 | "date": "2017-12-31", 18 | "distribution": "1", 19 | "info": "This is a test", 20 | "threat_level_id": "1" 21 | } 22 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/attribute_del.json: -------------------------------------------------------------------------------- 1 | { 2 | "Attribute": [ 3 | { 4 | "Tag": [ 5 | { 6 | "name": "osint" 7 | } 8 | ], 9 | "category": "Payload delivery", 10 | "deleted": true, 11 | "disable_correlation": false, 12 | "id": "42", 13 | "to_ids": true, 14 | "type": "filename", 15 | "value": "bar.exe" 16 | } 17 | ], 18 | "analysis": "1", 19 | "date": "2017-12-31", 20 | "distribution": "1", 21 | "info": "This is a test", 22 | "threat_level_id": "1" 23 | } 24 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/def_param.json: -------------------------------------------------------------------------------- 1 | { 2 | "Object": [ 3 | { 4 | "Attribute": [ 5 | { 6 | "category": "Attribution", 7 | "disable_correlation": false, 8 | "object_relation": "registrar", 9 | "to_ids": false, 10 | "type": "whois-registrar", 11 | "value": "registar.example.com" 12 | }, 13 | { 14 | "category": "Network activity", 15 | "disable_correlation": false, 16 | "object_relation": "domain", 17 | "to_ids": true, 18 | "type": "domain", 19 | "value": "domain.example.com" 20 | }, 21 | { 22 | "category": "Network activity", 23 | "disable_correlation": true, 24 | "object_relation": "nameserver", 25 | "to_ids": false, 26 | "type": "hostname", 27 | "value": "ns1.example.com" 28 | }, 29 | { 30 | "category": "External analysis", 31 | "disable_correlation": false, 32 | "object_relation": "nameserver", 33 | "to_ids": true, 34 | "type": "hostname", 35 | "value": "ns2.example.com" 36 | } 37 | ], 38 | "description": "Whois records information for a domain name or an IP address.", 39 | "distribution": "5", 40 | "meta-category": "network", 41 | "name": "whois", 42 | "sharing_group_id": "0", 43 | "template_uuid": "429faea1-34ff-47af-8a00-7c62d3be5a6a", 44 | "template_version": "10", 45 | "uuid": "a" 46 | } 47 | ], 48 | "analysis": "1", 49 | "date": "2017-12-31", 50 | "distribution": "1", 51 | "info": "This is a test", 52 | "threat_level_id": "1" 53 | } 54 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "analysis": "1", 3 | "date": "2017-12-31", 4 | "distribution": "1", 5 | "info": "This is a test", 6 | "published": true, 7 | "threat_level_id": "1" 8 | } 9 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/event_obj_attr_tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "Object": [ 3 | { 4 | "Attribute": [ 5 | { 6 | "Tag": [ 7 | { 8 | "name": "blah" 9 | } 10 | ], 11 | "category": "Payload delivery", 12 | "disable_correlation": true, 13 | "object_relation": "filename", 14 | "to_ids": true, 15 | "type": "filename", 16 | "value": "bar" 17 | } 18 | ], 19 | "ObjectReference": [ 20 | { 21 | "comment": "foo", 22 | "object_uuid": "a", 23 | "referenced_uuid": "b", 24 | "relationship_type": "baz" 25 | } 26 | ], 27 | "description": "File object describing a file with meta-information", 28 | "distribution": "5", 29 | "meta-category": "file", 30 | "name": "file", 31 | "sharing_group_id": "0", 32 | "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", 33 | "template_version": "25", 34 | "uuid": "a" 35 | }, 36 | { 37 | "Attribute": [ 38 | { 39 | "category": "Network activity", 40 | "disable_correlation": false, 41 | "object_relation": "url", 42 | "to_ids": true, 43 | "type": "url", 44 | "value": "https://www.circl.lu" 45 | } 46 | ], 47 | "description": "url object describes an url along with its normalized field (like extracted using faup parsing library) and its metadata.", 48 | "distribution": "5", 49 | "meta-category": "network", 50 | "name": "url", 51 | "sharing_group_id": "0", 52 | "template_uuid": "60efb77b-40b5-4c46-871b-ed1ed999fce5", 53 | "template_version": "10", 54 | "uuid": "b" 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/event_obj_def_param.json: -------------------------------------------------------------------------------- 1 | { 2 | "Object": [ 3 | { 4 | "Attribute": [ 5 | { 6 | "Tag": [ 7 | { 8 | "name": "blah" 9 | } 10 | ], 11 | "category": "Payload delivery", 12 | "disable_correlation": true, 13 | "object_relation": "filename", 14 | "to_ids": true, 15 | "type": "filename", 16 | "value": "bar" 17 | }, 18 | { 19 | "category": "Artifacts dropped", 20 | "disable_correlation": false, 21 | "object_relation": "pattern-in-file", 22 | "to_ids": true, 23 | "type": "pattern-in-file", 24 | "value": "baz" 25 | } 26 | ], 27 | "description": "File object describing a file with meta-information", 28 | "distribution": "5", 29 | "meta-category": "file", 30 | "name": "file", 31 | "sharing_group_id": "0", 32 | "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", 33 | "template_version": "25", 34 | "uuid": "a" 35 | }, 36 | { 37 | "Attribute": [ 38 | { 39 | "Tag": [ 40 | { 41 | "name": "blah" 42 | } 43 | ], 44 | "category": "Payload delivery", 45 | "disable_correlation": true, 46 | "object_relation": "filename", 47 | "to_ids": true, 48 | "type": "filename", 49 | "value": "baz" 50 | } 51 | ], 52 | "description": "File object describing a file with meta-information", 53 | "distribution": "5", 54 | "meta-category": "file", 55 | "name": "file", 56 | "sharing_group_id": "0", 57 | "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", 58 | "template_version": "25", 59 | "uuid": "b" 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/event_obj_tag.json: -------------------------------------------------------------------------------- 1 | { 2 | "Object": [ 3 | { 4 | "Attribute": [ 5 | { 6 | "category": "Payload delivery", 7 | "disable_correlation": false, 8 | "object_relation": "filename", 9 | "to_ids": true, 10 | "type": "filename", 11 | "value": "bar" 12 | } 13 | ], 14 | "Tag": [ 15 | { 16 | "name": "osint" 17 | } 18 | ], 19 | "description": "File object describing a file with meta-information", 20 | "distribution": 5, 21 | "meta-category": "file", 22 | "name": "file", 23 | "sharing_group_id": 0, 24 | "template_uuid": "688c46fb-5edb-40a3-8273-1af7923e2215", 25 | "template_version": 9, 26 | "uuid": "a" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/event_tags.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tag": [ 3 | { 4 | "name": "bar" 5 | }, 6 | { 7 | "name": "baz" 8 | }, 9 | { 10 | "name": "foo" 11 | } 12 | ], 13 | "analysis": "1", 14 | "date": "2017-12-31", 15 | "distribution": "1", 16 | "info": "This is a test", 17 | "threat_level_id": "1" 18 | } 19 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/galaxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "c5f2dfb4-21a1-42d8-a452-1d3c36a204ff", 3 | "name": "Tea Matrix", 4 | "type": "tea-matrix", 5 | "description": "Tea Matrix", 6 | "namespace": "tea-matrix", 7 | "GalaxyCluster": [ 8 | { 9 | "collection_uuid": "7eacd736-b093-4cc0-a56c-5f84de725dfb", 10 | "type": "tea-matrix", 11 | "value": "Milk in tea", 12 | "tag_name": "misp-galaxy:tea-matrix=\"Milk in tea\"", 13 | "description": "Milk in tea", 14 | "uuid": "24430dc6-9c27-4b3c-a5e7-6dda478fffa0", 15 | "distribution": "3", 16 | "default": true, 17 | "meta": { 18 | "kill_chain": [ 19 | "tea:black" 20 | ] 21 | }, 22 | "relationship_type": "ennemy-of" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/malware.json: -------------------------------------------------------------------------------- 1 | { 2 | "Attribute": [ 3 | { 4 | "category": "Payload delivery", 5 | "data": "ewp9Cg==", 6 | "disable_correlation": false, 7 | "encrypt": true, 8 | "malware_filename": "bar.exe", 9 | "to_ids": true, 10 | "type": "malware-sample", 11 | "value": "bar.exe" 12 | } 13 | ], 14 | "analysis": "1", 15 | "date": "2017-12-31", 16 | "distribution": "1", 17 | "info": "This is a test", 18 | "threat_level_id": "1" 19 | } 20 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/misp_custom_obj.json: -------------------------------------------------------------------------------- 1 | { 2 | "Object": [ 3 | { 4 | "Attribute": [ 5 | { 6 | "category": "Other", 7 | "disable_correlation": false, 8 | "object_relation": "member3", 9 | "to_ids": false, 10 | "type": "text", 11 | "value": "foo" 12 | }, 13 | { 14 | "category": "Other", 15 | "disable_correlation": false, 16 | "object_relation": "member1", 17 | "to_ids": false, 18 | "type": "text", 19 | "value": "bar" 20 | } 21 | ], 22 | "description": "TestTemplate.", 23 | "distribution": "5", 24 | "meta-category": "file", 25 | "name": "test_object_template", 26 | "sharing_group_id": "0", 27 | "template_uuid": "4ec55cc6-9e49-4c64-b794-03c25c1a6589", 28 | "template_version": "1", 29 | "uuid": "a" 30 | } 31 | ], 32 | "analysis": "1", 33 | "date": "2017-12-31", 34 | "distribution": "1", 35 | "info": "This is a test", 36 | "threat_level_id": "1" 37 | } 38 | 39 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/proposals.json: -------------------------------------------------------------------------------- 1 | { 2 | "Attribute": [ 3 | { 4 | "ShadowAttribute": [ 5 | { 6 | "category": "Payload delivery", 7 | "disable_correlation": false, 8 | "to_ids": true, 9 | "type": "filename", 10 | "value": "bar.pdf" 11 | } 12 | ], 13 | "category": "Payload delivery", 14 | "disable_correlation": false, 15 | "to_ids": true, 16 | "type": "filename", 17 | "value": "bar.exe" 18 | } 19 | ], 20 | "ShadowAttribute": [ 21 | { 22 | "category": "Payload delivery", 23 | "disable_correlation": false, 24 | "to_ids": true, 25 | "type": "filename", 26 | "value": "baz.jpg" 27 | } 28 | ], 29 | "analysis": "1", 30 | "date": "2017-12-31", 31 | "distribution": "1", 32 | "info": "This is a test", 33 | "threat_level_id": "1" 34 | } 35 | 36 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/sighting.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "11111111", 3 | "type": "bar", 4 | "value": "1" 5 | } 6 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /tests/mispevent_testfiles/test_object_template/definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "requiredOneOf": [ 3 | "member1", 4 | "member2" 5 | ], 6 | "required": [ 7 | "member3" 8 | ], 9 | "attributes": { 10 | "member1": { 11 | "description": "FirstMember", 12 | "misp-attribute": "text" 13 | }, 14 | "member2": { 15 | "description": "SecondMember", 16 | "misp-attribute": "text", 17 | "multiple": true 18 | }, 19 | "member3": { 20 | "description": "Thirdmember", 21 | "misp-attribute": "text" 22 | } 23 | }, 24 | "version": "1", 25 | "description": "TestTemplate.", 26 | "meta-category": "file", 27 | "uuid": "4ec55cc6-9e49-4c64-b794-03c25c1a6589", 28 | "name": "test_object_template" 29 | } 30 | -------------------------------------------------------------------------------- /tests/new_misp_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "Event": { 3 | "uuid": "57c06bb1-625c-4d34-9b9f-4066950d210f", 4 | "orgc_id": "1", 5 | "publish_timestamp": "0", 6 | "RelatedEvent": [], 7 | "org_id": "1", 8 | "Org": { 9 | "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f", 10 | "name": "CIRCL", 11 | "id": "1" 12 | }, 13 | "attribute_count": null, 14 | "distribution": "0", 15 | "sharing_group_id": "0", 16 | "threat_level_id": "1", 17 | "locked": false, 18 | "Attribute": [], 19 | "published": false, 20 | "ShadowAttribute": [], 21 | "date": "2016-08-26", 22 | "info": "This is a test", 23 | "timestamp": "1472228273", 24 | "Orgc": { 25 | "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f", 26 | "name": "CIRCL", 27 | "id": "1" 28 | }, 29 | "id": "594", 30 | "proposal_email_lock": false, 31 | "analysis": "0" 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /tests/reportlab_testoutputs/to_delete1.json.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/reportlab_testoutputs/to_delete1.json.pdf -------------------------------------------------------------------------------- /tests/reportlab_testoutputs/to_delete2.json.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/reportlab_testoutputs/to_delete2.json.pdf -------------------------------------------------------------------------------- /tests/reportlab_testoutputs/to_delete3.json.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/PyMISP/503a86442b4a1829faef5c32a2dcf145725e4b5f/tests/reportlab_testoutputs/to_delete3.json.pdf -------------------------------------------------------------------------------- /tests/search_index_result.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "3", 4 | "org": "", 5 | "date": "2016-12-01", 6 | "info": "Another random Event", 7 | "published": false, 8 | "uuid": "5758ebf5-c898-48e6-9fe9-5665c0a83866", 9 | "attribute_count": "2", 10 | "analysis": "0", 11 | "orgc": "", 12 | "timestamp": "1465681801", 13 | "distribution": "3", 14 | "proposal_email_lock": false, 15 | "locked": false, 16 | "threat_level_id": "1", 17 | "publish_timestamp": "0", 18 | "sharing_group_id": "0", 19 | "org_id": "1", 20 | "orgc_id": "1", 21 | "Org": { 22 | "id": "1", 23 | "name": "ORGNAME" 24 | }, 25 | "Orgc": { 26 | "id": "1", 27 | "name": "ORGNAME" 28 | }, 29 | "EventTag": [ 30 | { 31 | "id": "9760", 32 | "event_id": "6028", 33 | "tag_id": "4", 34 | "Tag": { 35 | "id": "4", 36 | "name": "TLP:GREEN", 37 | "colour": "#33822d", 38 | "exportable": true 39 | } 40 | }, 41 | { 42 | "id": "9801", 43 | "event_id": "3", 44 | "tag_id": "1", 45 | "Tag": { 46 | "id": "1", 47 | "name": "for_intelmq_processing", 48 | "colour": "#00ad1c", 49 | "exportable": true 50 | } 51 | }, 52 | { 53 | "id": "9803", 54 | "event_id": "3", 55 | "tag_id": "6", 56 | "Tag": { 57 | "id": "6", 58 | "name": "ecsirt:malicious-code=\"ransomware\"", 59 | "colour": "#005a5a", 60 | "exportable": true 61 | } 62 | } 63 | ], 64 | "SharingGroup": { 65 | "id": null, 66 | "name": null 67 | } 68 | } 69 | ] 70 | -------------------------------------------------------------------------------- /tests/sharing_groups.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "SharingGroup": { 4 | "id": "1", 5 | "name": "PrivateTrustedGroup", 6 | "description": "", 7 | "releasability": "", 8 | "local": true, 9 | "active": true 10 | }, 11 | "Organisation": { 12 | "id": "1", 13 | "name": "CIRCL", 14 | "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" 15 | }, 16 | "SharingGroupOrg": [ 17 | { 18 | "id": "1", 19 | "sharing_group_id": "1", 20 | "org_id": "1", 21 | "extend": true, 22 | "Organisation": { 23 | "name": "CIRCL", 24 | "id": "1", 25 | "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" 26 | } 27 | }, 28 | { 29 | "id": "2", 30 | "sharing_group_id": "1", 31 | "org_id": "2", 32 | "extend": false, 33 | "Organisation": { 34 | "name": "PifPafPoum", 35 | "id": "2", 36 | "uuid": "56bf12a7-c19c-4b98-83e7-d9bb02de0b81" 37 | } 38 | } 39 | ], 40 | "SharingGroupServer": [ 41 | { 42 | "all_orgs": false, 43 | "server_id": "0", 44 | "sharing_group_id": "1", 45 | "Server": [] 46 | } 47 | ], 48 | "editable": true 49 | }, 50 | { 51 | "SharingGroup": { 52 | "id": "2", 53 | "name": "test", 54 | "description": "", 55 | "releasability": "", 56 | "local": true, 57 | "active": true 58 | }, 59 | "Organisation": { 60 | "id": "1", 61 | "name": "CIRCL", 62 | "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" 63 | }, 64 | "SharingGroupOrg": [ 65 | { 66 | "id": "3", 67 | "sharing_group_id": "2", 68 | "org_id": "1", 69 | "extend": true, 70 | "Organisation": { 71 | "name": "CIRCL", 72 | "id": "1", 73 | "uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f" 74 | } 75 | }, 76 | { 77 | "id": "4", 78 | "sharing_group_id": "2", 79 | "org_id": "2", 80 | "extend": false, 81 | "Organisation": { 82 | "name": "PifPafPoum", 83 | "id": "2", 84 | "uuid": "56bf12a7-c19c-4b98-83e7-d9bb02de0b81" 85 | } 86 | } 87 | ], 88 | "SharingGroupServer": [ 89 | { 90 | "all_orgs": false, 91 | "server_id": "0", 92 | "sharing_group_id": "2", 93 | "Server": [] 94 | } 95 | ], 96 | "editable": true 97 | } 98 | ] 99 | -------------------------------------------------------------------------------- /tests/stix2.json: -------------------------------------------------------------------------------- 1 | {"type": "bundle", "spec_version": "2.0", "id": "bundle--811070d5-8e95-43c1-8af3-0b5e799dc15c", "objects": [{"type": "identity", "id": "identity--5d397c4b-3474-466a-80cd-624838d4ff94", "name": "ORGNAME", "identity_class": "organization", "created": "2021-08-24T10:53:42.879Z", "modified": "2021-08-24T10:53:42.879Z"}, {"type": "report", "id": "report--f90bb8c1-8505-4d74-af34-3dcffec6b6d4", "name": "Test Stix", "created": "2021-08-24T00:00:00.000Z", "published": "2021-08-24T10:53:28Z", "modified": "2021-08-24T10:53:13.000Z", "created_by_ref": "identity--5d397c4b-3474-466a-80cd-624838d4ff94", "labels": ["Threat-Report", "misp:tool=\"misp2stix2\""], "object_refs": ["observed-data--0853d51f-0fe7-4d35-b3cb-b96bdbc1f0ee"]}, {"id": "observed-data--0853d51f-0fe7-4d35-b3cb-b96bdbc1f0ee", "type": "observed-data", "number_observed": 1, "objects": {"0": {"type": "ipv4-addr", "value": "8.8.8.8"}, "1": {"type": "network-traffic", "src_ref": "0", "protocols": ["ipv4"]}}, "created_by_ref": "identity--5d397c4b-3474-466a-80cd-624838d4ff94", "labels": ["misp:type=\"ip-src\"", "misp:category=\"Network activity\"", "misp:to_ids=\"False\""], "created": "2021-08-24T10:53:13.000Z", "modified": "2021-08-24T10:53:13.000Z", "first_observed": "2021-08-24T10:53:13Z", "last_observed": "2021-08-24T10:53:13Z"}]} 2 | -------------------------------------------------------------------------------- /tests/test_fileobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import annotations 4 | 5 | import unittest 6 | import json 7 | from pymisp.tools import FileObject 8 | import pathlib 9 | 10 | 11 | class TestFileObject(unittest.TestCase): 12 | def test_mimeType(self) -> None: 13 | file_object = FileObject(filepath=pathlib.Path(__file__)) 14 | attributes = json.loads(file_object.to_json())['Attribute'] 15 | mime = next(attr for attr in attributes if attr['object_relation'] == 'mimetype') 16 | # was "Python script, ASCII text executable" 17 | # libmagic on linux: 'text/x-python' 18 | # libmagic on os x: 'text/x-script.python' 19 | self.assertEqual(mime['value'][:7], 'text/x-') 20 | self.assertEqual(mime['value'][-6:], 'python') 21 | --------------------------------------------------------------------------------