mirror of https://github.com/MISP/PyMISP
Merge branch 'main' of github.com:misp/pymisp
commit
afb1c8f2d0
|
|
@ -42,7 +42,7 @@ jobs:
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
|
@ -56,7 +56,7 @@ jobs:
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
@ -69,6 +69,6 @@ jobs:
|
||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.8, 3.9, '3.10', '3.11']
|
python-version: [3.8, 3.9, '3.10', '3.11', '3.12']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set up Python ${{matrix.python-version}}
|
- name: Set up Python ${{matrix.python-version}}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{matrix.python-version}}
|
python-version: ${{matrix.python-version}}
|
||||||
|
|
||||||
|
|
@ -34,7 +34,12 @@ jobs:
|
||||||
- name: Test with nosetests
|
- name: Test with nosetests
|
||||||
run: |
|
run: |
|
||||||
poetry run pytest --cov=pymisp tests/test_*.py
|
poetry run pytest --cov=pymisp tests/test_*.py
|
||||||
poetry run mypy tests/testlive_comprehensive.py tests/test_mispevent.py tests/testlive_sync.py pymisp
|
poetry run mypy .
|
||||||
|
|
||||||
|
- name: Test with nosetests with orjson
|
||||||
|
run: |
|
||||||
|
pip3 install orjson
|
||||||
|
poetry run pytest --cov=pymisp tests/test_*.py
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v4
|
||||||
|
|
|
||||||
247
CHANGELOG.txt
247
CHANGELOG.txt
|
|
@ -2,6 +2,250 @@ Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
|
||||||
|
v2.4.185 (2024-02-16)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Changes
|
||||||
|
~~~~~~~
|
||||||
|
- Bump deps, version. [Raphaël Vinot]
|
||||||
|
|
||||||
|
|
||||||
|
v2.4.184.3 (2024-02-12)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Changes
|
||||||
|
~~~~~~~
|
||||||
|
- Bump changelog. [Raphaël Vinot]
|
||||||
|
- Bump version. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
|
||||||
|
Fix
|
||||||
|
~~~
|
||||||
|
- Properly get body from message, without headers. [Raphaël Vinot]
|
||||||
|
- Remove from __all__ entries that shouldn't be there. [Raphaël Vinot]
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- Build(deps-dev): bump jupyter-lsp from 2.2.1 to 2.2.2.
|
||||||
|
[dependabot[bot]]
|
||||||
|
|
||||||
|
Bumps [jupyter-lsp](https://github.com/jupyter-lsp/jupyterlab-lsp) from 2.2.1 to 2.2.2.
|
||||||
|
- [Release notes](https://github.com/jupyter-lsp/jupyterlab-lsp/releases)
|
||||||
|
- [Changelog](https://github.com/jupyter-lsp/jupyterlab-lsp/blob/main/CHANGELOG.md)
|
||||||
|
- [Commits](https://github.com/jupyter-lsp/jupyterlab-lsp/commits)
|
||||||
|
|
||||||
|
---
|
||||||
|
updated-dependencies:
|
||||||
|
- dependency-name: jupyter-lsp
|
||||||
|
dependency-type: indirect
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
v2.4.184.2 (2024-02-06)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Changes
|
||||||
|
~~~~~~~
|
||||||
|
- Add changelog. [Raphaël Vinot]
|
||||||
|
- Bump changelog. [Raphaël Vinot]
|
||||||
|
- Re-add ExpandedPyMISP, with a warning. [Raphaël Vinot]
|
||||||
|
|
||||||
|
Fix
|
||||||
|
~~~
|
||||||
|
- Do not throw a warning every time one import pymisp... [Raphaël Vinot]
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- Build(deps): bump codecov/codecov-action from 3 to 4.
|
||||||
|
[dependabot[bot]]
|
||||||
|
|
||||||
|
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
|
||||||
|
- [Release notes](https://github.com/codecov/codecov-action/releases)
|
||||||
|
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
|
||||||
|
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)
|
||||||
|
|
||||||
|
---
|
||||||
|
updated-dependencies:
|
||||||
|
- dependency-name: codecov/codecov-action
|
||||||
|
dependency-type: direct:production
|
||||||
|
update-type: version-update:semver-major
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
v2.4.184.1 (2024-02-06)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Changes
|
||||||
|
~~~~~~~
|
||||||
|
- Bump changelog. [Raphaël Vinot]
|
||||||
|
- Re-add ExpandedPyMISP, with a warning. [Raphaël Vinot]
|
||||||
|
|
||||||
|
|
||||||
|
v2.4.184 (2024-02-02)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
New
|
||||||
|
~~~
|
||||||
|
- Enable support for python 3.12. [Raphaël Vinot]
|
||||||
|
- Relationship_type in tag. [Raphaël Vinot]
|
||||||
|
|
||||||
|
Fix https://github.com/MISP/MISP/issues/9483
|
||||||
|
- [internal] Add support for orjson. [Jakub Onderka]
|
||||||
|
|
||||||
|
orjson is much faster library for decoding and encoding JSON formats
|
||||||
|
|
||||||
|
Changes
|
||||||
|
~~~~~~~
|
||||||
|
- Bump changelog. [Raphaël Vinot]
|
||||||
|
- Bump deps, version, objects. [Raphaël Vinot]
|
||||||
|
- Remove IntEnum. [Raphaël Vinot]
|
||||||
|
- Add even more debug for gha. [Raphaël Vinot]
|
||||||
|
- Add some debug for gha. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
- Add more strict typing, not done yet. [Raphaël Vinot]
|
||||||
|
- Add a bunch more typing. [Raphaël Vinot]
|
||||||
|
- Use typing info of lief. [Raphaël Vinot]
|
||||||
|
- First batch of changes for strict typing. [Raphaël Vinot]
|
||||||
|
- Update typing to please lief. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
- [internal] Simplify code. [Jakub Onderka]
|
||||||
|
- [internal] User faster method to convert bytes to str. [Jakub Onderka]
|
||||||
|
- New annotations in tests. [Raphaël Vinot]
|
||||||
|
- Initial changes to use new annotations. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
- Bump deps, try to install with python 3.12. [Raphaël Vinot]
|
||||||
|
- Make the publish_timestamp a string, as per specs. [Raphaël Vinot]
|
||||||
|
- [internal] Update poetry.lock. [Jakub Onderka]
|
||||||
|
|
||||||
|
Fix
|
||||||
|
~~~
|
||||||
|
- Revert typing changes. [Raphaël Vinot]
|
||||||
|
- More responses athat are lists. [Raphaël Vinot]
|
||||||
|
- Another call that cn be a list or a dict. [Raphaël Vinot]
|
||||||
|
- Do not cast enum. [Raphaël Vinot]
|
||||||
|
- More fixes to support responses from MISP. [Raphaël Vinot]
|
||||||
|
- Handle list responses properly. [Raphaël Vinot]
|
||||||
|
- Import FileObject as needed. [Raphaël Vinot]
|
||||||
|
- Also skip docs from mypy. [Raphaël Vinot]
|
||||||
|
- Run mypy on what I want. [Raphaël Vinot]
|
||||||
|
- Compatibility with python 3.8. [Raphaël Vinot]
|
||||||
|
- Python < 3.10 support on typing, for good. [Raphaël Vinot]
|
||||||
|
- Python < 3.10 support on typing. [Raphaël Vinot]
|
||||||
|
- Rollback tests on python 3.12 as lief is not supported yet. [Raphaël
|
||||||
|
Vinot]
|
||||||
|
- Add missing wheel. [Raphaël Vinot]
|
||||||
|
- Make publish_timestamp a string in tests. [Raphaël Vinot]
|
||||||
|
- [internal] README typos. [Jakub Onderka]
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- Revert "fix: More responses athat are lists" [Raphaël Vinot]
|
||||||
|
|
||||||
|
This reverts commit 709a10c64c0513b515f25c3ecfb9eb577b55084b.
|
||||||
|
- Build(deps): bump jinja2 from 3.1.2 to 3.1.3. [dependabot[bot]]
|
||||||
|
|
||||||
|
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3.
|
||||||
|
- [Release notes](https://github.com/pallets/jinja/releases)
|
||||||
|
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
|
||||||
|
- [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3)
|
||||||
|
|
||||||
|
---
|
||||||
|
updated-dependencies:
|
||||||
|
- dependency-name: jinja2
|
||||||
|
dependency-type: indirect
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
v2.4.183 (2024-01-04)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
New
|
||||||
|
~~~
|
||||||
|
- Documentation to install PyMISP on offline machine. [Raphaël Vinot]
|
||||||
|
|
||||||
|
Changes
|
||||||
|
~~~~~~~
|
||||||
|
- Bump changelog. [Raphaël Vinot]
|
||||||
|
- Bump objects. [Raphaël Vinot]
|
||||||
|
- Bump version. [Raphaël Vinot]
|
||||||
|
- Remove jsonschema from dependencies. [Raphaël Vinot]
|
||||||
|
- Encrypt malicious js. [Raphaël Vinot]
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- Fix api ssl verify typing. [Steven]
|
||||||
|
- Add HTTPS Adapter. [Steven]
|
||||||
|
|
||||||
|
Add the ability to provide a custom HTTPS adapter to the PyMISP class. With M2Crypto and m2requests, this can enable mutual TLS with hardware tokens.
|
||||||
|
|
||||||
|
|
||||||
|
v2.4.182 (2023-12-14)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Changes
|
||||||
|
~~~~~~~
|
||||||
|
- Bump changelog. [Raphaël Vinot]
|
||||||
|
- Bump version. [Raphaël Vinot]
|
||||||
|
- Bump objects. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
|
||||||
|
Fix
|
||||||
|
~~~
|
||||||
|
- Avoid exception when the malware file name contains a "|" [Raphaël
|
||||||
|
Vinot]
|
||||||
|
|
||||||
|
Other
|
||||||
|
~~~~~
|
||||||
|
- Build(deps): bump github/codeql-action from 2 to 3. [dependabot[bot]]
|
||||||
|
|
||||||
|
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
|
||||||
|
- [Release notes](https://github.com/github/codeql-action/releases)
|
||||||
|
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
|
||||||
|
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)
|
||||||
|
|
||||||
|
---
|
||||||
|
updated-dependencies:
|
||||||
|
- dependency-name: github/codeql-action
|
||||||
|
dependency-type: direct:production
|
||||||
|
update-type: version-update:semver-major
|
||||||
|
...
|
||||||
|
- Build(deps): bump actions/setup-python from 4 to 5. [dependabot[bot]]
|
||||||
|
|
||||||
|
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
|
||||||
|
- [Release notes](https://github.com/actions/setup-python/releases)
|
||||||
|
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)
|
||||||
|
|
||||||
|
---
|
||||||
|
updated-dependencies:
|
||||||
|
- dependency-name: actions/setup-python
|
||||||
|
dependency-type: direct:production
|
||||||
|
update-type: version-update:semver-major
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
v2.4.179 (2023-11-23)
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Changes
|
||||||
|
~~~~~~~
|
||||||
|
- Bump version, changelog. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
- [misp-objects] Bumped latest version. [Christian Studer]
|
||||||
|
|
||||||
|
Fix
|
||||||
|
~~~
|
||||||
|
- Eml and msg are in sync again. [Raphaël Vinot]
|
||||||
|
- Update calls to getStringStream to the public method. [Raphaël Vinot]
|
||||||
|
- Avoid confusing error when an auth key is limited to an IP. [Raphaël
|
||||||
|
Vinot]
|
||||||
|
|
||||||
|
Fix #1099
|
||||||
|
|
||||||
|
|
||||||
v2.4.178 (2023-10-24)
|
v2.4.178 (2023-10-24)
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|
@ -11,6 +255,7 @@ New
|
||||||
|
|
||||||
Changes
|
Changes
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
- Bump changelog. [Raphaël Vinot]
|
||||||
- Bump version, make __version__ dynamic. [Raphaël Vinot]
|
- Bump version, make __version__ dynamic. [Raphaël Vinot]
|
||||||
- Bump deps, allow older jsonschema for compatibility. [Raphaël Vinot]
|
- Bump deps, allow older jsonschema for compatibility. [Raphaël Vinot]
|
||||||
- Bump deps. [Raphaël Vinot]
|
- Bump deps. [Raphaël Vinot]
|
||||||
|
|
@ -5080,5 +5325,3 @@ v1.1.2 (2015-08-05)
|
||||||
- Json export is not supported everywhere. [Raphaël Vinot]
|
- Json export is not supported everywhere. [Raphaël Vinot]
|
||||||
- Some testing. [Raphaël Vinot]
|
- Some testing. [Raphaël Vinot]
|
||||||
- Initial commit. [Raphaël Vinot]
|
- Initial commit. [Raphaël Vinot]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
30
README.md
30
README.md
|
|
@ -125,7 +125,6 @@ logging.basicConfig(level=logging.DEBUG, filename="debug.log", filemode='w', for
|
||||||
# From poetry
|
# From poetry
|
||||||
|
|
||||||
pytest --cov=pymisp tests/test_*.py tests/testlive_comprehensive.py:TestComprehensive.[test_name]
|
pytest --cov=pymisp tests/test_*.py tests/testlive_comprehensive.py:TestComprehensive.[test_name]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
@ -159,6 +158,35 @@ Your new MISPObject generator must generate attributes and add them as class pro
|
||||||
|
|
||||||
When the object is sent to MISP, all the class properties will be exported to the JSON export.
|
When the object is sent to MISP, all the class properties will be exported to the JSON export.
|
||||||
|
|
||||||
|
## Installing PyMISP on a machine with no internet access
|
||||||
|
|
||||||
|
This is done using poetry and you need to have this repository cloned on your machine.
|
||||||
|
The commands below have to be run from inside the cloned directory.
|
||||||
|
|
||||||
|
|
||||||
|
1. From a machine with access to the internet, get the dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir offline
|
||||||
|
poetry export --all-extras > offline/requirements.txt
|
||||||
|
poetry run pip download -r offline/requirements.txt -d offline/packages/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Prepare the PyMISP Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poetry build
|
||||||
|
mv dist/*.whl offline/packages/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Copy the content of `offline/packages/` to the machine with no internet access.
|
||||||
|
|
||||||
|
4. Install the packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install --no-index --no-deps packages/*.whl
|
||||||
|
```
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
PyMISP is distributed under an [open source license](./LICENSE). A simplified 2-BSD license.
|
PyMISP is distributed under an [open source license](./LICENSE). A simplified 2-BSD license.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from pymisp import ExpandedPyMISP
|
from pymisp import PyMISP
|
||||||
from pymisp.tools import EMailObject
|
from pymisp.tools import EMailObject
|
||||||
import traceback
|
import traceback
|
||||||
from keys import misp_url, misp_key, misp_verifycert # type: ignore
|
from keys import misp_url, misp_key, misp_verifycert # type: ignore
|
||||||
|
|
@ -15,7 +14,7 @@ if __name__ == '__main__':
|
||||||
parser.add_argument("-p", "--path", required=True, help="Path to process (expanded using glob).")
|
parser.add_argument("-p", "--path", required=True, help="Path to process (expanded using glob).")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
pymisp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert, debug=True)
|
pymisp = PyMISP(misp_url, misp_key, misp_verifycert, debug=True)
|
||||||
|
|
||||||
for f in glob.glob(args.path):
|
for f in glob.glob(args.path):
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
15
mypy.ini
15
mypy.ini
|
|
@ -1,6 +1,15 @@
|
||||||
[mypy]
|
[mypy]
|
||||||
ignore_errors = False
|
strict = True
|
||||||
|
warn_return_any = False
|
||||||
show_error_context = True
|
show_error_context = True
|
||||||
pretty = True
|
pretty = True
|
||||||
exclude = pymisp/data|example|docs
|
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
|
||||||
|
|
||||||
|
# Stuff to remove gradually
|
||||||
|
disallow_untyped_defs = False
|
||||||
|
disallow_untyped_calls = False
|
||||||
|
disable_error_code = arg-type,return-value,assignment,call-overload,union-attr
|
||||||
|
|
||||||
|
|
||||||
|
[mypy-docs.source.*]
|
||||||
|
ignore_errors = True
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
@ -9,7 +11,7 @@ logger = logging.getLogger(__name__)
|
||||||
__version__ = importlib.metadata.version("pymisp")
|
__version__ = importlib.metadata.version("pymisp")
|
||||||
|
|
||||||
|
|
||||||
def warning_2024():
|
def warning_2024() -> None:
|
||||||
if sys.version_info < (3, 10):
|
if sys.version_info < (3, 10):
|
||||||
warnings.warn("""
|
warnings.warn("""
|
||||||
As our baseline system is the latest Ubuntu LTS, and Ubuntu LTS 22.04 has Python 3.10 available,
|
As our baseline system is the latest Ubuntu LTS, and Ubuntu LTS 22.04 has Python 3.10 available,
|
||||||
|
|
@ -38,17 +40,14 @@ try:
|
||||||
MISPEventDelegation, MISPUserSetting, MISPInbox, MISPEventBlocklist, MISPOrganisationBlocklist,
|
MISPEventDelegation, MISPUserSetting, MISPInbox, MISPEventBlocklist, MISPOrganisationBlocklist,
|
||||||
MISPEventReport, MISPCorrelationExclusion, MISPDecayingModel, MISPGalaxy, MISPGalaxyCluster,
|
MISPEventReport, MISPCorrelationExclusion, MISPDecayingModel, MISPGalaxy, MISPGalaxyCluster,
|
||||||
MISPGalaxyClusterElement, MISPGalaxyClusterRelation)
|
MISPGalaxyClusterElement, MISPGalaxyClusterRelation)
|
||||||
|
from .api import PyMISP, register_user # noqa
|
||||||
|
# NOTE: the direct imports to .tools are kept for backward compatibility but should be removed in the future
|
||||||
from .tools import AbstractMISPObjectGenerator # noqa
|
from .tools import AbstractMISPObjectGenerator # noqa
|
||||||
from .tools import Neo4j # noqa
|
|
||||||
from .tools import stix # noqa
|
|
||||||
from .tools import openioc # noqa
|
from .tools import openioc # noqa
|
||||||
from .tools import ext_lookups # noqa
|
from .tools import ext_lookups # noqa
|
||||||
from .tools import update_objects # noqa
|
from .tools import update_objects # noqa
|
||||||
|
|
||||||
from .api import PyMISP, register_user # noqa
|
|
||||||
from .api import PyMISP as ExpandedPyMISP # noqa
|
|
||||||
from .tools import load_warninglists # noqa
|
from .tools import load_warninglists # noqa
|
||||||
# Let's not bother with old python
|
|
||||||
try:
|
try:
|
||||||
from .tools import reportlab_generator # noqa
|
from .tools import reportlab_generator # noqa
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -59,4 +58,25 @@ try:
|
||||||
pass
|
pass
|
||||||
logger.debug('pymisp loaded properly')
|
logger.debug('pymisp loaded properly')
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logger.warning('Unable to load pymisp properly: {}'.format(e))
|
logger.warning(f'Unable to load pymisp properly: {e}')
|
||||||
|
|
||||||
|
|
||||||
|
class ExpandedPyMISP(PyMISP):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
warnings.warn('This class is deprecated, use PyMISP instead', FutureWarning)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['PyMISP', 'register_user', 'AbstractMISP', 'MISPTag',
|
||||||
|
'MISPEvent', 'MISPAttribute', 'MISPObjectReference', 'MISPObjectAttribute',
|
||||||
|
'MISPObject', 'MISPUser', 'MISPOrganisation', 'MISPSighting', 'MISPLog',
|
||||||
|
'MISPShadowAttribute', 'MISPWarninglist', 'MISPTaxonomy', 'MISPNoticelist',
|
||||||
|
'MISPObjectTemplate', 'MISPSharingGroup', 'MISPRole', 'MISPServer', 'MISPFeed',
|
||||||
|
'MISPEventDelegation', 'MISPUserSetting', 'MISPInbox', 'MISPEventBlocklist',
|
||||||
|
'MISPOrganisationBlocklist', 'MISPEventReport', 'MISPCorrelationExclusion',
|
||||||
|
'MISPDecayingModel', 'MISPGalaxy', 'MISPGalaxyCluster', 'MISPGalaxyClusterElement',
|
||||||
|
'MISPGalaxyClusterRelation', 'PyMISPError', 'NewEventError', 'NewAttributeError',
|
||||||
|
'NoURL', 'NoKey', 'InvalidMISPObject', 'UnknownMISPObjectTemplate', 'PyMISPInvalidFormat',
|
||||||
|
'Distribution', 'ThreatLevel', 'Analysis', 'ExpandedPyMISP'
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,48 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
|
||||||
from deprecated import deprecated # type: ignore
|
from deprecated import deprecated # type: ignore
|
||||||
from json import JSONEncoder
|
from json import JSONEncoder
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from abc import ABCMeta
|
from abc import ABCMeta
|
||||||
|
from enum import Enum, IntEnum
|
||||||
try:
|
from typing import Any, Mapping
|
||||||
from rapidjson import load # type: ignore
|
|
||||||
from rapidjson import loads # type: ignore
|
|
||||||
from rapidjson import dumps # type: ignore
|
|
||||||
HAS_RAPIDJSON = True
|
|
||||||
except ImportError:
|
|
||||||
from json import load
|
|
||||||
from json import loads
|
|
||||||
from json import dumps
|
|
||||||
HAS_RAPIDJSON = False
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from enum import Enum
|
|
||||||
from typing import Union, Optional, Any, Dict, List, Set, Mapping
|
|
||||||
|
|
||||||
from .exceptions import PyMISPInvalidFormat, PyMISPError
|
|
||||||
|
|
||||||
|
|
||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
import orjson # type: ignore
|
||||||
|
from orjson import loads, dumps
|
||||||
|
HAS_ORJSON = True
|
||||||
|
except ImportError:
|
||||||
|
from json import loads, dumps
|
||||||
|
HAS_ORJSON = False
|
||||||
|
|
||||||
|
from .exceptions import PyMISPInvalidFormat, PyMISPError
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
resources_path = Path(__file__).parent / 'data'
|
resources_path = Path(__file__).parent / 'data'
|
||||||
misp_objects_path = resources_path / 'misp-objects' / 'objects'
|
misp_objects_path = resources_path / 'misp-objects' / 'objects'
|
||||||
with (resources_path / 'describeTypes.json').open('r') as f:
|
with (resources_path / 'describeTypes.json').open('rb') as f:
|
||||||
describe_types = load(f)['result']
|
describe_types: dict[str, Any] = loads(f.read())['result']
|
||||||
|
|
||||||
|
|
||||||
class MISPFileCache(object):
|
class MISPFileCache:
|
||||||
# cache up to 150 JSON structures in class attribute
|
# cache up to 150 JSON structures in class attribute
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@lru_cache(maxsize=150)
|
@lru_cache(maxsize=150)
|
||||||
def _load_json(path: Path) -> Union[dict, None]:
|
def _load_json(path: Path) -> dict[str, Any] | None:
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return None
|
return None
|
||||||
with path.open('r', encoding='utf-8') as f:
|
with path.open('rb') as f:
|
||||||
data = load(f)
|
data = loads(f.read())
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -73,7 +68,7 @@ class Analysis(Enum):
|
||||||
completed = 2
|
completed = 2
|
||||||
|
|
||||||
|
|
||||||
def _int_to_str(d: Dict[str, Any]) -> Dict[str, Any]:
|
def _int_to_str(d: dict[str, Any]) -> dict[str, Any]:
|
||||||
# transform all integer back to string
|
# transform all integer back to string
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
|
|
@ -85,7 +80,7 @@ def _int_to_str(d: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|
||||||
@deprecated(reason=" Use method default=pymisp_json_default instead of cls=MISPEncode", version='2.4.117', action='default')
|
@deprecated(reason=" Use method default=pymisp_json_default instead of cls=MISPEncode", version='2.4.117', action='default')
|
||||||
class MISPEncode(JSONEncoder):
|
class MISPEncode(JSONEncoder):
|
||||||
def default(self, obj):
|
def default(self, obj: Any) -> dict[str, Any] | str:
|
||||||
if isinstance(obj, AbstractMISP):
|
if isinstance(obj, AbstractMISP):
|
||||||
return obj.jsonable()
|
return obj.jsonable()
|
||||||
elif isinstance(obj, (datetime, date)):
|
elif isinstance(obj, (datetime, date)):
|
||||||
|
|
@ -97,12 +92,12 @@ class MISPEncode(JSONEncoder):
|
||||||
return JSONEncoder.default(self, obj)
|
return JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
|
||||||
class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): # type: ignore[type-arg]
|
||||||
__resources_path = resources_path
|
__resources_path = resources_path
|
||||||
__misp_objects_path = misp_objects_path
|
__misp_objects_path = misp_objects_path
|
||||||
__describe_types = describe_types
|
__describe_types = describe_types
|
||||||
|
|
||||||
def __init__(self, **kwargs: Dict):
|
def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
"""Abstract class for all the MISP objects.
|
"""Abstract class for all the MISP objects.
|
||||||
NOTE: Every method in every classes inheriting this one are doing
|
NOTE: Every method in every classes inheriting this one are doing
|
||||||
changes in memory and do not modify data on a remote MISP instance.
|
changes in memory and do not modify data on a remote MISP instance.
|
||||||
|
|
@ -111,9 +106,9 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.__edited: bool = True # As we create a new object, we assume it is edited
|
self.__edited: bool = True # As we create a new object, we assume it is edited
|
||||||
self.__not_jsonable: List[str] = []
|
self.__not_jsonable: list[str] = []
|
||||||
self._fields_for_feed: Set
|
self._fields_for_feed: set[str]
|
||||||
self.__self_defined_describe_types: Optional[Dict] = None
|
self.__self_defined_describe_types: dict[str, Any] | None = None
|
||||||
self.uuid: str
|
self.uuid: str
|
||||||
|
|
||||||
if kwargs.get('force_timestamps') is not None:
|
if kwargs.get('force_timestamps') is not None:
|
||||||
|
|
@ -123,13 +118,13 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
self.__force_timestamps = False
|
self.__force_timestamps = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def describe_types(self) -> Dict:
|
def describe_types(self) -> dict[str, Any]:
|
||||||
if self.__self_defined_describe_types:
|
if self.__self_defined_describe_types:
|
||||||
return self.__self_defined_describe_types
|
return self.__self_defined_describe_types
|
||||||
return self.__describe_types
|
return self.__describe_types
|
||||||
|
|
||||||
@describe_types.setter
|
@describe_types.setter
|
||||||
def describe_types(self, describe_types: Dict):
|
def describe_types(self, describe_types: dict[str, Any]) -> None:
|
||||||
self.__self_defined_describe_types = describe_types
|
self.__self_defined_describe_types = describe_types
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -141,12 +136,12 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
return self.__misp_objects_path
|
return self.__misp_objects_path
|
||||||
|
|
||||||
@misp_objects_path.setter
|
@misp_objects_path.setter
|
||||||
def misp_objects_path(self, misp_objects_path: Union[str, Path]):
|
def misp_objects_path(self, misp_objects_path: str | Path) -> None:
|
||||||
if isinstance(misp_objects_path, str):
|
if isinstance(misp_objects_path, str):
|
||||||
misp_objects_path = Path(misp_objects_path)
|
misp_objects_path = Path(misp_objects_path)
|
||||||
self.__misp_objects_path = misp_objects_path
|
self.__misp_objects_path = misp_objects_path
|
||||||
|
|
||||||
def from_dict(self, **kwargs) -> None:
|
def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
"""Loading all the parameters as class properties, if they aren't `None`.
|
"""Loading all the parameters as class properties, if they aren't `None`.
|
||||||
This method aims to be called when all the properties requiring a special
|
This method aims to be called when all the properties requiring a special
|
||||||
treatment are processed.
|
treatment are processed.
|
||||||
|
|
@ -159,15 +154,15 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
# We load an existing dictionary, marking it an not-edited
|
# We load an existing dictionary, marking it an not-edited
|
||||||
self.__edited = False
|
self.__edited = False
|
||||||
|
|
||||||
def update_not_jsonable(self, *args) -> None:
|
def update_not_jsonable(self, *args) -> None: # type: ignore[no-untyped-def]
|
||||||
"""Add entries to the __not_jsonable list"""
|
"""Add entries to the __not_jsonable list"""
|
||||||
self.__not_jsonable += args
|
self.__not_jsonable += args
|
||||||
|
|
||||||
def set_not_jsonable(self, args: List[str]) -> None:
|
def set_not_jsonable(self, args: list[str]) -> None:
|
||||||
"""Set __not_jsonable to a new list"""
|
"""Set __not_jsonable to a new list"""
|
||||||
self.__not_jsonable = args
|
self.__not_jsonable = args
|
||||||
|
|
||||||
def _remove_from_not_jsonable(self, *args) -> None:
|
def _remove_from_not_jsonable(self, *args) -> None: # type: ignore[no-untyped-def]
|
||||||
"""Remove the entries that are in the __not_jsonable list"""
|
"""Remove the entries that are in the __not_jsonable list"""
|
||||||
for entry in args:
|
for entry in args:
|
||||||
try:
|
try:
|
||||||
|
|
@ -179,7 +174,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
"""Load a JSON string"""
|
"""Load a JSON string"""
|
||||||
self.from_dict(**loads(json_string))
|
self.from_dict(**loads(json_string))
|
||||||
|
|
||||||
def to_dict(self, json_format: bool = False) -> Dict:
|
def to_dict(self, json_format: bool = False) -> dict[str, Any]:
|
||||||
"""Dump the class to a dictionary.
|
"""Dump the class to a dictionary.
|
||||||
This method automatically removes the timestamp recursively in every object
|
This method automatically removes the timestamp recursively in every object
|
||||||
that has been edited is order to let MISP update the event accordingly."""
|
that has been edited is order to let MISP update the event accordingly."""
|
||||||
|
|
@ -221,15 +216,15 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
to_return = _int_to_str(to_return)
|
to_return = _int_to_str(to_return)
|
||||||
return to_return
|
return to_return
|
||||||
|
|
||||||
def jsonable(self) -> Dict:
|
def jsonable(self) -> dict[str, Any]:
|
||||||
"""This method is used by the JSON encoder"""
|
"""This method is used by the JSON encoder"""
|
||||||
return self.to_dict()
|
return self.to_dict()
|
||||||
|
|
||||||
def _to_feed(self) -> Dict:
|
def _to_feed(self) -> dict[str, Any]:
|
||||||
if not hasattr(self, '_fields_for_feed') or not self._fields_for_feed:
|
if not hasattr(self, '_fields_for_feed') or not self._fields_for_feed:
|
||||||
raise PyMISPError('Unable to export in the feed format, _fields_for_feed is missing.')
|
raise PyMISPError('Unable to export in the feed format, _fields_for_feed is missing.')
|
||||||
if hasattr(self, '_set_default') and callable(self._set_default): # type: ignore
|
if hasattr(self, '_set_default') and callable(self._set_default):
|
||||||
self._set_default() # type: ignore
|
self._set_default()
|
||||||
to_return = {}
|
to_return = {}
|
||||||
for field in sorted(self._fields_for_feed):
|
for field in sorted(self._fields_for_feed):
|
||||||
if getattr(self, field, None) is not None:
|
if getattr(self, field, None) is not None:
|
||||||
|
|
@ -243,15 +238,25 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
if field in ['data', 'first_seen', 'last_seen', 'deleted']:
|
if field in ['data', 'first_seen', 'last_seen', 'deleted']:
|
||||||
# special fields
|
# special fields
|
||||||
continue
|
continue
|
||||||
raise PyMISPError('The field {} is required in {} when generating a feed.'.format(field, self.__class__.__name__))
|
raise PyMISPError(f'The field {field} is required in {self.__class__.__name__} when generating a feed.')
|
||||||
to_return = _int_to_str(to_return)
|
to_return = _int_to_str(to_return)
|
||||||
return to_return
|
return to_return
|
||||||
|
|
||||||
def to_json(self, sort_keys: bool = False, indent: Optional[int] = None) -> str:
|
def to_json(self, sort_keys: bool = False, indent: int | None = None) -> str:
|
||||||
"""Dump recursively any class of type MISPAbstract to a json string"""
|
"""Dump recursively any class of type MISPAbstract to a json string"""
|
||||||
|
if HAS_ORJSON:
|
||||||
|
option = 0
|
||||||
|
if sort_keys:
|
||||||
|
option |= orjson.OPT_SORT_KEYS
|
||||||
|
if indent:
|
||||||
|
option |= orjson.OPT_INDENT_2
|
||||||
|
# orjson dumps method returns bytes instead of bytes, to keep compatibility with json
|
||||||
|
# we have to convert output to str
|
||||||
|
return str(dumps(self, default=pymisp_json_default, option=option))
|
||||||
|
|
||||||
return dumps(self, default=pymisp_json_default, sort_keys=sort_keys, indent=indent)
|
return dumps(self, default=pymisp_json_default, sort_keys=sort_keys, indent=indent)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key: str) -> Any:
|
||||||
try:
|
try:
|
||||||
if key[0] != '_' and key not in self.__not_jsonable:
|
if key[0] != '_' and key not in self.__not_jsonable:
|
||||||
return self.__dict__[key]
|
return self.__dict__[key]
|
||||||
|
|
@ -260,13 +265,13 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
# Expected by pop and other dict-related methods
|
# Expected by pop and other dict-related methods
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key: str) -> None:
|
||||||
delattr(self, key)
|
delattr(self, key)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> Any:
|
||||||
'''When we call **self, skip keys:
|
'''When we call **self, skip keys:
|
||||||
* starting with _
|
* starting with _
|
||||||
* in __not_jsonable
|
* in __not_jsonable
|
||||||
|
|
@ -285,7 +290,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
return self.__force_timestamps
|
return self.__force_timestamps
|
||||||
|
|
||||||
@force_timestamp.setter
|
@force_timestamp.setter
|
||||||
def force_timestamp(self, force: bool):
|
def force_timestamp(self, force: bool) -> None:
|
||||||
self.__force_timestamps = force
|
self.__force_timestamps = force
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -305,28 +310,28 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
return self.__edited
|
return self.__edited
|
||||||
|
|
||||||
@edited.setter
|
@edited.setter
|
||||||
def edited(self, val: bool):
|
def edited(self, val: bool) -> None:
|
||||||
"""Set the edit flag"""
|
"""Set the edit flag"""
|
||||||
if isinstance(val, bool):
|
if isinstance(val, bool):
|
||||||
self.__edited = val
|
self.__edited = val
|
||||||
else:
|
else:
|
||||||
raise PyMISPError('edited can only be True or False')
|
raise PyMISPError('edited can only be True or False')
|
||||||
|
|
||||||
def __setattr__(self, name: str, value: Any):
|
def __setattr__(self, name: str, value: Any) -> None:
|
||||||
if name[0] != '_' and not self.__edited and name in self:
|
if name[0] != '_' and not self.__edited and name in self:
|
||||||
# The private members don't matter
|
# The private members don't matter
|
||||||
# If we already have a key with that name, we're modifying it.
|
# If we already have a key with that name, we're modifying it.
|
||||||
self.__edited = True
|
self.__edited = True
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
def _datetime_to_timestamp(self, d: Union[int, float, str, datetime]) -> int:
|
def _datetime_to_timestamp(self, d: int | float | str | datetime) -> int:
|
||||||
"""Convert a datetime object to a timestamp (int)"""
|
"""Convert a datetime object to a timestamp (int)"""
|
||||||
if isinstance(d, (int, float, str)):
|
if isinstance(d, (int, float, str)):
|
||||||
# Assume we already have a timestamp
|
# Assume we already have a timestamp
|
||||||
return int(d)
|
return int(d)
|
||||||
return int(d.timestamp())
|
return int(d.timestamp())
|
||||||
|
|
||||||
def _add_tag(self, tag: Optional[Union[str, 'MISPTag', Mapping]] = None, **kwargs):
|
def _add_tag(self, tag: str | MISPTag | Mapping[str, Any] | None = None, **kwargs): # type: ignore[no-untyped-def]
|
||||||
"""Add a tag to the attribute (by name or a MISPTag object)"""
|
"""Add a tag to the attribute (by name or a MISPTag object)"""
|
||||||
if isinstance(tag, str):
|
if isinstance(tag, str):
|
||||||
misp_tag = MISPTag()
|
misp_tag = MISPTag()
|
||||||
|
|
@ -346,14 +351,14 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
self.edited = True
|
self.edited = True
|
||||||
return misp_tag
|
return misp_tag
|
||||||
|
|
||||||
def _set_tags(self, tags: List['MISPTag']):
|
def _set_tags(self, tags: list[MISPTag]) -> None:
|
||||||
"""Set a list of prepared MISPTag."""
|
"""Set a list of prepared MISPTag."""
|
||||||
if all(isinstance(x, MISPTag) for x in tags):
|
if all(isinstance(x, MISPTag) for x in tags):
|
||||||
self.Tag = tags
|
self.Tag = tags
|
||||||
else:
|
else:
|
||||||
raise PyMISPInvalidFormat('All the attributes have to be of type MISPTag.')
|
raise PyMISPInvalidFormat('All the attributes have to be of type MISPTag.')
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
if isinstance(other, AbstractMISP):
|
if isinstance(other, AbstractMISP):
|
||||||
return self.to_dict() == other.to_dict()
|
return self.to_dict() == other.to_dict()
|
||||||
elif isinstance(other, dict):
|
elif isinstance(other, dict):
|
||||||
|
|
@ -362,26 +367,26 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return '<{self.__class__.__name__} - please define me>'.format(self=self)
|
return f'<{self.__class__.__name__} - please define me>'
|
||||||
|
|
||||||
|
|
||||||
class MISPTag(AbstractMISP):
|
class MISPTag(AbstractMISP):
|
||||||
|
|
||||||
_fields_for_feed: set = {'name', 'colour', 'relationship_type', 'local'}
|
_fields_for_feed: set[str] = {'name', 'colour', 'relationship_type', 'local'}
|
||||||
|
|
||||||
def __init__(self, **kwargs: Dict):
|
def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.name: str
|
self.name: str
|
||||||
self.exportable: bool
|
self.exportable: bool
|
||||||
self.local: bool
|
self.local: bool
|
||||||
self.relationship_type: Optional[str]
|
self.relationship_type: str | None
|
||||||
|
|
||||||
def from_dict(self, **kwargs):
|
def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
if kwargs.get('Tag'):
|
if kwargs.get('Tag'):
|
||||||
kwargs = kwargs.get('Tag')
|
kwargs = kwargs.get('Tag') # type: ignore[assignment]
|
||||||
super().from_dict(**kwargs)
|
super().from_dict(**kwargs)
|
||||||
|
|
||||||
def _set_default(self):
|
def _set_default(self) -> None:
|
||||||
if not hasattr(self, 'relationship_type'):
|
if not hasattr(self, 'relationship_type'):
|
||||||
self.relationship_type = ''
|
self.relationship_type = ''
|
||||||
if not hasattr(self, 'colour'):
|
if not hasattr(self, 'colour'):
|
||||||
|
|
@ -389,40 +394,30 @@ class MISPTag(AbstractMISP):
|
||||||
if not hasattr(self, 'local'):
|
if not hasattr(self, 'local'):
|
||||||
self.local = False
|
self.local = False
|
||||||
|
|
||||||
def _to_feed(self, with_local: bool = True) -> Dict:
|
def _to_feed(self, with_local: bool = True) -> dict[str, Any]:
|
||||||
if hasattr(self, 'exportable') and not self.exportable:
|
if hasattr(self, 'exportable') and not self.exportable:
|
||||||
return {}
|
return {}
|
||||||
if with_local is False and hasattr(self, 'local') and self.local:
|
if with_local is False and hasattr(self, 'local') and self.local:
|
||||||
return {}
|
return {}
|
||||||
return super()._to_feed()
|
return super()._to_feed()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self) -> None:
|
||||||
self.deleted = True
|
self.deleted = True
|
||||||
self.edited = True
|
self.edited = True
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
if hasattr(self, 'name'):
|
if hasattr(self, 'name'):
|
||||||
return '<{self.__class__.__name__}(name={self.name})>'.format(self=self)
|
return '<{self.__class__.__name__}(name={self.name})>'.format(self=self)
|
||||||
return '<{self.__class__.__name__}(NotInitialized)>'.format(self=self)
|
return f'<{self.__class__.__name__}(NotInitialized)>'
|
||||||
|
|
||||||
|
|
||||||
if HAS_RAPIDJSON:
|
# UUID, datetime, date and Enum is serialized by ORJSON by default
|
||||||
def pymisp_json_default(obj: Union[AbstractMISP, datetime, date, Enum, UUID]) -> Union[Dict, str]:
|
def pymisp_json_default(obj: AbstractMISP | datetime | date | Enum | UUID) -> dict[str, Any] | str:
|
||||||
if isinstance(obj, AbstractMISP):
|
if isinstance(obj, AbstractMISP):
|
||||||
return obj.jsonable()
|
return obj.jsonable()
|
||||||
elif isinstance(obj, (datetime, date)):
|
elif isinstance(obj, (datetime, date)):
|
||||||
return obj.isoformat()
|
return obj.isoformat()
|
||||||
elif isinstance(obj, Enum):
|
elif isinstance(obj, Enum):
|
||||||
return obj.value
|
return obj.value
|
||||||
elif isinstance(obj, UUID):
|
elif isinstance(obj, UUID):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
else:
|
|
||||||
def pymisp_json_default(obj: Union[AbstractMISP, datetime, date, Enum, UUID]) -> Union[Dict, str]:
|
|
||||||
if isinstance(obj, AbstractMISP):
|
|
||||||
return obj.jsonable()
|
|
||||||
elif isinstance(obj, (datetime, date)):
|
|
||||||
return obj.isoformat()
|
|
||||||
elif isinstance(obj, Enum):
|
|
||||||
return obj.value
|
|
||||||
elif isinstance(obj, UUID):
|
|
||||||
return str(obj)
|
|
||||||
|
|
|
||||||
936
pymisp/api.py
936
pymisp/api.py
File diff suppressed because it is too large
Load Diff
|
|
@ -1 +1 @@
|
||||||
Subproject commit ca371d456712d0484a7585fbe1bcad4128272512
|
Subproject commit 3ac509965fdbca06d8a027db22c0064588babd3c
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
class PyMISPError(Exception):
|
class PyMISPError(Exception):
|
||||||
def __init__(self, message):
|
def __init__(self, message: str) -> None:
|
||||||
super(PyMISPError, self).__init__(message)
|
super().__init__(message)
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from .vtreportobject import VTReportObject # noqa
|
from .vtreportobject import VTReportObject # noqa
|
||||||
from .neo4j import Neo4j # noqa
|
from .neo4j import Neo4j # noqa
|
||||||
from .fileobject import FileObject # noqa
|
from .fileobject import FileObject # noqa
|
||||||
|
|
@ -41,3 +43,13 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Requires lief, optional [fileobjects]
|
# Requires lief, optional [fileobjects]
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
__all__ = ['VTReportObject', 'Neo4j', 'FileObject', 'make_binary_objects',
|
||||||
|
'AbstractMISPObjectGenerator', 'GenericObjectGenerator',
|
||||||
|
'load_openioc', 'load_openioc_file', 'SBSignatureObject',
|
||||||
|
'Fail2BanObject', 'DomainIPObject', 'ASNObject', 'GeolocationObject',
|
||||||
|
'GitVulnFinderObject', 'VehicleObject', 'CSVLoader',
|
||||||
|
'SSHAuthorizedKeysObject', 'feed_meta_generator', 'update_objects',
|
||||||
|
'EMailObject', 'URLObject', 'PEObject', 'PESectionObject', 'ELFObject',
|
||||||
|
'ELFSectionObject', 'MachOObject', 'MachOSectionObject'
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,37 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import socket
|
import socket
|
||||||
import idna
|
import idna
|
||||||
from publicsuffixlist import PublicSuffixList # type: ignore
|
from publicsuffixlist import PublicSuffixList # type: ignore
|
||||||
from urllib.parse import urlparse, urlunparse
|
from urllib.parse import urlparse, urlunparse, ParseResult
|
||||||
|
|
||||||
|
|
||||||
class UrlNotDecoded(Exception):
|
class UrlNotDecoded(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PSLFaup(object):
|
class PSLFaup:
|
||||||
"""
|
"""
|
||||||
Fake Faup Python Library using PSL for Windows support
|
Fake Faup Python Library using PSL for Windows support
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.decoded = False
|
self.decoded = False
|
||||||
self.psl = PublicSuffixList()
|
self.psl = PublicSuffixList()
|
||||||
self._url = None
|
self._url: ParseResult | None = None
|
||||||
self._retval = {}
|
self._retval: dict[str, str | int | None | bytes] = {}
|
||||||
self.ip_as_host = False
|
self.ip_as_host = ''
|
||||||
|
|
||||||
def _clear(self):
|
def _clear(self) -> None:
|
||||||
self.decoded = False
|
self.decoded = False
|
||||||
self._url = None
|
self._url = None
|
||||||
self._retval = {}
|
self._retval = {}
|
||||||
self.ip_as_host = False
|
self.ip_as_host = ''
|
||||||
|
|
||||||
def decode(self, url) -> None:
|
def decode(self, url: str) -> None:
|
||||||
"""
|
"""
|
||||||
This function creates a dict of all the url fields.
|
This function creates a dict of all the url fields.
|
||||||
:param url: The URL to normalize
|
:param url: The URL to normalize
|
||||||
|
|
@ -42,10 +43,15 @@ class PSLFaup(object):
|
||||||
url = '//' + url
|
url = '//' + url
|
||||||
self._url = urlparse(url)
|
self._url = urlparse(url)
|
||||||
|
|
||||||
self.ip_as_host = False
|
if self._url is None:
|
||||||
|
raise UrlNotDecoded("Unable to parse URL")
|
||||||
|
|
||||||
|
self.ip_as_host = ''
|
||||||
|
if self._url.hostname is None:
|
||||||
|
raise UrlNotDecoded("Unable to parse URL")
|
||||||
hostname = _ensure_str(self._url.hostname)
|
hostname = _ensure_str(self._url.hostname)
|
||||||
try:
|
try:
|
||||||
ipv4_bytes = socket.inet_aton(_ensure_str(hostname))
|
ipv4_bytes = socket.inet_aton(hostname)
|
||||||
ipv4 = ipaddress.IPv4Address(ipv4_bytes)
|
ipv4 = ipaddress.IPv4Address(ipv4_bytes)
|
||||||
self.ip_as_host = ipv4.compressed
|
self.ip_as_host = ipv4.compressed
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
|
|
@ -60,61 +66,70 @@ class PSLFaup(object):
|
||||||
self._retval = {}
|
self._retval = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self) -> bytes | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
netloc = self.get_host() + ('' if self.get_port() is None else ':{}'.format(self.get_port()))
|
if host := self.get_host():
|
||||||
return _ensure_bytes(
|
netloc = host + ('' if self.get_port() is None else f':{self.get_port()}')
|
||||||
urlunparse(
|
return _ensure_bytes(
|
||||||
(self.get_scheme(), netloc, self.get_resource_path(),
|
urlunparse(
|
||||||
'', self.get_query_string(), self.get_fragment(),)
|
(self.get_scheme(), netloc, self.get_resource_path(),
|
||||||
|
'', self.get_query_string(), self.get_fragment(),)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
return None
|
||||||
|
|
||||||
def get_scheme(self):
|
def get_scheme(self) -> str:
|
||||||
"""
|
"""
|
||||||
Get the scheme of the url given in the decode function
|
Get the scheme of the url given in the decode function
|
||||||
:returns: The URL scheme
|
:returns: The URL scheme
|
||||||
"""
|
"""
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
return _ensure_str(self._url.scheme)
|
return _ensure_str(self._url.scheme if self._url.scheme else '')
|
||||||
|
|
||||||
def get_credential(self):
|
def get_credential(self) -> str | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
if self._url.password:
|
if self._url.username and self._url.password:
|
||||||
return _ensure_str(self._url.username) + ':' + _ensure_str(self._url.password)
|
return _ensure_str(self._url.username) + ':' + _ensure_str(self._url.password)
|
||||||
if self._url.username:
|
if self._url.username:
|
||||||
return _ensure_str(self._url.username)
|
return _ensure_str(self._url.username)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_subdomain(self):
|
def get_subdomain(self) -> str | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
if self.get_host() is not None and not self.ip_as_host:
|
if self.get_host() is not None and not self.ip_as_host:
|
||||||
if self.get_domain() in self.get_host():
|
domain = self.get_domain()
|
||||||
return self.get_host().rsplit(self.get_domain(), 1)[0].rstrip('.') or None
|
host = self.get_host()
|
||||||
|
if domain and host and domain in host:
|
||||||
|
return host.rsplit(domain, 1)[0].rstrip('.') or None
|
||||||
|
return None
|
||||||
|
|
||||||
def get_domain(self):
|
def get_domain(self) -> str | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
if self.get_host() is not None and not self.ip_as_host:
|
if self.get_host() is not None and not self.ip_as_host:
|
||||||
return self.psl.privatesuffix(self.get_host())
|
return self.psl.privatesuffix(self.get_host())
|
||||||
|
return None
|
||||||
|
|
||||||
def get_domain_without_tld(self):
|
def get_domain_without_tld(self) -> str | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
if self.get_tld() is not None and not self.ip_as_host:
|
if self.get_tld() is not None and not self.ip_as_host:
|
||||||
return self.get_domain().rsplit(self.get_tld(), 1)[0].rstrip('.')
|
if domain := self.get_domain():
|
||||||
|
return domain.rsplit(self.get_tld(), 1)[0].rstrip('.')
|
||||||
|
return None
|
||||||
|
|
||||||
def get_host(self):
|
def get_host(self) -> str | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
if self._url.hostname is None:
|
if self._url.hostname is None:
|
||||||
|
|
@ -124,45 +139,48 @@ class PSLFaup(object):
|
||||||
else:
|
else:
|
||||||
return _ensure_str(idna.encode(self._url.hostname, uts46=True))
|
return _ensure_str(idna.encode(self._url.hostname, uts46=True))
|
||||||
|
|
||||||
def get_unicode_host(self):
|
def get_unicode_host(self) -> str | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
if not self.ip_as_host:
|
if not self.ip_as_host:
|
||||||
return idna.decode(self.get_host(), uts46=True)
|
if host := self.get_host():
|
||||||
|
return idna.decode(host, uts46=True)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_tld(self):
|
def get_tld(self) -> str | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
if self.get_host() is not None and not self.ip_as_host:
|
if self.get_host() is not None and not self.ip_as_host:
|
||||||
return self.psl.publicsuffix(self.get_host())
|
return self.psl.publicsuffix(self.get_host())
|
||||||
|
return None
|
||||||
|
|
||||||
def get_port(self):
|
def get_port(self) -> int | None:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
return self._url.port
|
return self._url.port
|
||||||
|
|
||||||
def get_resource_path(self):
|
def get_resource_path(self) -> str:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
return _ensure_str(self._url.path)
|
return _ensure_str(self._url.path)
|
||||||
|
|
||||||
def get_query_string(self):
|
def get_query_string(self) -> str:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
return _ensure_str(self._url.query)
|
return _ensure_str(self._url.query)
|
||||||
|
|
||||||
def get_fragment(self):
|
def get_fragment(self) -> str:
|
||||||
if not self.decoded:
|
if not self.decoded or not self._url:
|
||||||
raise UrlNotDecoded("You must call faup.decode() first")
|
raise UrlNotDecoded("You must call faup.decode() first")
|
||||||
|
|
||||||
return _ensure_str(self._url.fragment)
|
return _ensure_str(self._url.fragment)
|
||||||
|
|
||||||
def get(self):
|
def get(self) -> dict[str, str | int | None | bytes]:
|
||||||
self._retval["scheme"] = self.get_scheme()
|
self._retval["scheme"] = self.get_scheme()
|
||||||
self._retval["tld"] = self.get_tld()
|
self._retval["tld"] = self.get_tld()
|
||||||
self._retval["domain"] = self.get_domain()
|
self._retval["domain"] = self.get_domain()
|
||||||
|
|
@ -177,14 +195,14 @@ class PSLFaup(object):
|
||||||
return self._retval
|
return self._retval
|
||||||
|
|
||||||
|
|
||||||
def _ensure_bytes(binary) -> bytes:
|
def _ensure_bytes(binary: str | bytes) -> bytes:
|
||||||
if isinstance(binary, bytes):
|
if isinstance(binary, bytes):
|
||||||
return binary
|
return binary
|
||||||
else:
|
else:
|
||||||
return binary.encode('utf-8')
|
return binary.encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def _ensure_str(string) -> str:
|
def _ensure_str(string: str | bytes) -> str:
|
||||||
if isinstance(string, str):
|
if isinstance(string, str):
|
||||||
return string
|
return string
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime, date
|
||||||
|
from dateutil.parser import parse
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .. import MISPObject
|
from .. import MISPObject
|
||||||
from ..exceptions import InvalidMISPObject
|
from ..exceptions import InvalidMISPObject
|
||||||
from datetime import datetime, date
|
|
||||||
from dateutil.parser import parse
|
|
||||||
from typing import Union, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractMISPObjectGenerator(MISPObject):
|
class AbstractMISPObjectGenerator(MISPObject):
|
||||||
|
|
||||||
def _detect_epoch(self, timestamp: Union[str, int, float]) -> bool:
|
def _detect_epoch(self, timestamp: str | int | float) -> bool:
|
||||||
try:
|
try:
|
||||||
tmp = float(timestamp)
|
tmp = float(timestamp)
|
||||||
if tmp < 30000000:
|
if tmp < 30000000:
|
||||||
|
|
@ -21,7 +24,7 @@ class AbstractMISPObjectGenerator(MISPObject):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _sanitize_timestamp(self, timestamp: Optional[Union[datetime, date, dict, str, int, float]] = None) -> datetime:
|
def _sanitize_timestamp(self, timestamp: datetime | date | dict[str, Any] | str | int | float | None = None) -> datetime:
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
return datetime.now()
|
return datetime.now()
|
||||||
|
|
||||||
|
|
@ -42,9 +45,9 @@ class AbstractMISPObjectGenerator(MISPObject):
|
||||||
else:
|
else:
|
||||||
raise Exception(f'Unable to convert {timestamp} to a datetime.')
|
raise Exception(f'Unable to convert {timestamp} to a datetime.')
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
"""Contains the logic where all the values of the object are gathered"""
|
"""Contains the logic where all the values of the object are gathered"""
|
||||||
if hasattr(self, '_parameters'):
|
if hasattr(self, '_parameters') and self._definition is not None:
|
||||||
for object_relation in self._definition['attributes']:
|
for object_relation in self._definition['attributes']:
|
||||||
value = self._parameters.pop(object_relation, None)
|
value = self._parameters.pop(object_relation, None)
|
||||||
if not value:
|
if not value:
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
class ASNObject(AbstractMISPObjectGenerator):
|
class ASNObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parameters: dict, strict: bool = True, **kwargs):
|
def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('asn', strict=strict, **kwargs)
|
super().__init__('asn', strict=strict, **kwargs)
|
||||||
self._parameters = parameters
|
self._parameters = parameters
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
first = self._sanitize_timestamp(self._parameters.pop('first-seen', None))
|
first = self._sanitize_timestamp(self._parameters.pop('first-seen', None))
|
||||||
self._parameters['first-seen'] = first
|
self._parameters['first-seen'] = first
|
||||||
last = self._sanitize_timestamp(self._parameters.pop('last-seen', None))
|
last = self._sanitize_timestamp(self._parameters.pop('last-seen', None))
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,25 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
from . import FileObject
|
|
||||||
from ..exceptions import MISPObjectException
|
from ..exceptions import MISPObjectException
|
||||||
import logging
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import lief
|
import lief
|
||||||
|
import lief.logging
|
||||||
lief.logging.disable()
|
lief.logging.disable()
|
||||||
HAS_LIEF = True
|
HAS_LIEF = True
|
||||||
|
|
||||||
from .peobject import make_pe_objects
|
from .peobject import make_pe_objects
|
||||||
from .elfobject import make_elf_objects
|
from .elfobject import make_elf_objects
|
||||||
from .machoobject import make_macho_objects
|
from .machoobject import make_macho_objects
|
||||||
|
from . import FileObject
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
HAS_LIEF = False
|
HAS_LIEF = False
|
||||||
|
|
@ -26,19 +28,29 @@ except AttributeError:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_LIEF = False
|
HAS_LIEF = False
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from . import PEObject, ELFObject, MachOObject, PESectionObject, ELFSectionObject, MachOSectionObject
|
||||||
|
|
||||||
|
|
||||||
class FileTypeNotImplemented(MISPObjectException):
|
class FileTypeNotImplemented(MISPObjectException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def make_binary_objects(filepath: Optional[str] = None, pseudofile: Optional[BytesIO] = None, filename: Optional[str] = None, standalone: bool = True, default_attributes_parameters: dict = {}):
|
def make_binary_objects(filepath: str | None = None,
|
||||||
|
pseudofile: BytesIO | bytes | None = None,
|
||||||
|
filename: str | None = None,
|
||||||
|
standalone: bool = True,
|
||||||
|
default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, PEObject | ELFObject | MachOObject | None, list[PESectionObject] | list[ELFSectionObject] | list[MachOSectionObject]]:
|
||||||
misp_file = FileObject(filepath=filepath, pseudofile=pseudofile, filename=filename,
|
misp_file = FileObject(filepath=filepath, pseudofile=pseudofile, filename=filename,
|
||||||
standalone=standalone, default_attributes_parameters=default_attributes_parameters)
|
standalone=standalone, default_attributes_parameters=default_attributes_parameters)
|
||||||
if HAS_LIEF and (filepath or (pseudofile and filename)):
|
if HAS_LIEF and (filepath or pseudofile):
|
||||||
if filepath:
|
if filepath:
|
||||||
lief_parsed = lief.parse(filepath=filepath)
|
lief_parsed = lief.parse(filepath=filepath)
|
||||||
elif pseudofile and filename:
|
elif pseudofile:
|
||||||
lief_parsed = lief.parse(raw=pseudofile.getvalue(), name=filename)
|
if isinstance(pseudofile, bytes):
|
||||||
|
lief_parsed = lief.parse(raw=pseudofile)
|
||||||
|
else: # BytesIO
|
||||||
|
lief_parsed = lief.parse(obj=pseudofile)
|
||||||
else:
|
else:
|
||||||
logger.critical('You need either a filepath, or a pseudofile and a filename.')
|
logger.critical('You need either a filepath, or a pseudofile and a filename.')
|
||||||
lief_parsed = None
|
lief_parsed = None
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
from pymisp import MISPObject
|
from pymisp import MISPObject
|
||||||
|
|
@ -10,8 +10,9 @@ from pymisp import MISPObject
|
||||||
|
|
||||||
class CSVLoader():
|
class CSVLoader():
|
||||||
|
|
||||||
def __init__(self, template_name: str, csv_path: Path, fieldnames: Optional[List[str]] = None, has_fieldnames=False,
|
def __init__(self, template_name: str, csv_path: Path,
|
||||||
delimiter: str = ',', quotechar: str = '"'):
|
fieldnames: list[str] | None = None, has_fieldnames: bool=False,
|
||||||
|
delimiter: str = ',', quotechar: str = '"') -> None:
|
||||||
self.template_name = template_name
|
self.template_name = template_name
|
||||||
self.delimiter = delimiter
|
self.delimiter = delimiter
|
||||||
self.quotechar = quotechar
|
self.quotechar = quotechar
|
||||||
|
|
@ -25,7 +26,7 @@ class CSVLoader():
|
||||||
else:
|
else:
|
||||||
self.has_fieldnames = has_fieldnames
|
self.has_fieldnames = has_fieldnames
|
||||||
|
|
||||||
def load(self):
|
def load(self) -> list[MISPObject]:
|
||||||
|
|
||||||
objects = []
|
objects = []
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ class CSVLoader():
|
||||||
# Check if the CSV file has a header, and if it matches with the object template
|
# Check if the CSV file has a header, and if it matches with the object template
|
||||||
tmp_object = MISPObject(self.template_name)
|
tmp_object = MISPObject(self.template_name)
|
||||||
|
|
||||||
if not tmp_object._definition['attributes']:
|
if not tmp_object._definition or not tmp_object._definition['attributes']:
|
||||||
raise Exception(f'Unable to find the object template ({self.template_name}), impossible to create objects.')
|
raise Exception(f'Unable to find the object template ({self.template_name}), impossible to create objects.')
|
||||||
allowed_fieldnames = list(tmp_object._definition['attributes'].keys())
|
allowed_fieldnames = list(tmp_object._definition['attributes'].keys())
|
||||||
for fieldname in self.fieldnames:
|
for fieldname in self.fieldnames:
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
class DomainIPObject(AbstractMISPObjectGenerator):
|
class DomainIPObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parameters: dict, strict: bool = True, **kwargs):
|
def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('domain-ip', strict=strict, **kwargs)
|
super().__init__('domain-ip', strict=strict, **kwargs)
|
||||||
self._parameters = parameters
|
self._parameters = parameters
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
first = self._sanitize_timestamp(self._parameters.pop('first-seen', None))
|
first = self._sanitize_timestamp(self._parameters.pop('first-seen', None))
|
||||||
self._parameters['first-seen'] = first
|
self._parameters['first-seen'] = first
|
||||||
last = self._sanitize_timestamp(self._parameters.pop('last-seen', None))
|
last = self._sanitize_timestamp(self._parameters.pop('last-seen', None))
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from hashlib import md5, sha1, sha256, sha512
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from . import FileObject
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
from ..exceptions import InvalidMISPObject
|
from ..exceptions import InvalidMISPObject
|
||||||
from io import BytesIO
|
|
||||||
from hashlib import md5, sha1, sha256, sha512
|
|
||||||
import logging
|
|
||||||
from typing import Union, Optional
|
|
||||||
from pathlib import Path
|
|
||||||
from . import FileObject
|
|
||||||
|
|
||||||
import lief
|
import lief
|
||||||
|
|
||||||
|
|
@ -21,7 +24,10 @@ except ImportError:
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
def make_elf_objects(lief_parsed: lief.ELF.Binary, misp_file: FileObject, standalone: bool = True, default_attributes_parameters: dict = {}):
|
def make_elf_objects(lief_parsed: lief.ELF.Binary,
|
||||||
|
misp_file: FileObject,
|
||||||
|
standalone: bool = True,
|
||||||
|
default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, ELFObject, list[ELFSectionObject]]:
|
||||||
elf_object = ELFObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters)
|
elf_object = ELFObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters)
|
||||||
misp_file.add_reference(elf_object.uuid, 'includes', 'ELF indicators')
|
misp_file.add_reference(elf_object.uuid, 'includes', 'ELF indicators')
|
||||||
elf_sections = []
|
elf_sections = []
|
||||||
|
|
@ -32,29 +38,39 @@ def make_elf_objects(lief_parsed: lief.ELF.Binary, misp_file: FileObject, standa
|
||||||
|
|
||||||
class ELFObject(AbstractMISPObjectGenerator):
|
class ELFObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parsed: Optional[lief.ELF.Binary] = None, filepath: Optional[Union[Path, str]] = None, pseudofile: Optional[BytesIO] = None, **kwargs):
|
__elf: lief.ELF.Binary
|
||||||
|
|
||||||
|
def __init__(self, parsed: lief.ELF.Binary | None = None, # type: ignore[no-untyped-def]
|
||||||
|
filepath: Path | str | None = None,
|
||||||
|
pseudofile: BytesIO | bytes | list[int] | None = None, **kwargs) -> None:
|
||||||
"""Creates an ELF object, with lief"""
|
"""Creates an ELF object, with lief"""
|
||||||
super().__init__('elf', **kwargs)
|
super().__init__('elf', **kwargs)
|
||||||
if not HAS_PYDEEP:
|
if not HAS_PYDEEP:
|
||||||
logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]")
|
logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]")
|
||||||
if pseudofile:
|
if pseudofile:
|
||||||
if isinstance(pseudofile, BytesIO):
|
if isinstance(pseudofile, BytesIO):
|
||||||
self.__elf = lief.ELF.parse(io=pseudofile)
|
e = lief.ELF.parse(obj=pseudofile)
|
||||||
elif isinstance(pseudofile, bytes):
|
elif isinstance(pseudofile, bytes):
|
||||||
self.__elf = lief.ELF.parse(raw=pseudofile)
|
e = lief.ELF.parse(raw=list(pseudofile))
|
||||||
|
elif isinstance(pseudofile, list):
|
||||||
|
e = lief.ELF.parse(raw=pseudofile)
|
||||||
else:
|
else:
|
||||||
raise InvalidMISPObject('Pseudo file can be BytesIO or bytes got {}'.format(type(pseudofile)))
|
raise InvalidMISPObject(f'Pseudo file can be BytesIO or bytes got {type(pseudofile)}')
|
||||||
|
if not e:
|
||||||
|
raise InvalidMISPObject('Unable to parse pseudofile')
|
||||||
|
self.__elf = e
|
||||||
elif filepath:
|
elif filepath:
|
||||||
self.__elf = lief.ELF.parse(filepath)
|
if e := lief.ELF.parse(filepath):
|
||||||
|
self.__elf = e
|
||||||
elif parsed:
|
elif parsed:
|
||||||
# Got an already parsed blob
|
# Got an already parsed blob
|
||||||
if isinstance(parsed, lief.ELF.Binary):
|
if isinstance(parsed, lief.ELF.Binary):
|
||||||
self.__elf = parsed
|
self.__elf = parsed
|
||||||
else:
|
else:
|
||||||
raise InvalidMISPObject('Not a lief.ELF.Binary: {}'.format(type(parsed)))
|
raise InvalidMISPObject(f'Not a lief.ELF.Binary: {type(parsed)}')
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
# General information
|
# General information
|
||||||
self.add_attribute('type', value=str(self.__elf.header.file_type).split('.')[1])
|
self.add_attribute('type', value=str(self.__elf.header.file_type).split('.')[1])
|
||||||
self.add_attribute('entrypoint-address', value=self.__elf.entrypoint)
|
self.add_attribute('entrypoint-address', value=self.__elf.entrypoint)
|
||||||
|
|
@ -68,7 +84,7 @@ class ELFObject(AbstractMISPObjectGenerator):
|
||||||
if not section.name:
|
if not section.name:
|
||||||
continue
|
continue
|
||||||
s = ELFSectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters)
|
s = ELFSectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters)
|
||||||
self.add_reference(s.uuid, 'includes', 'Section {} of ELF'.format(pos))
|
self.add_reference(s.uuid, 'includes', f'Section {pos} of ELF')
|
||||||
pos += 1
|
pos += 1
|
||||||
self.sections.append(s)
|
self.sections.append(s)
|
||||||
self.add_attribute('number-sections', value=len(self.sections))
|
self.add_attribute('number-sections', value=len(self.sections))
|
||||||
|
|
@ -76,22 +92,22 @@ class ELFObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
class ELFSectionObject(AbstractMISPObjectGenerator):
|
class ELFSectionObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, section: lief.ELF.Section, **kwargs):
|
def __init__(self, section: lief.ELF.Section, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
"""Creates an ELF Section object. Object generated by ELFObject."""
|
"""Creates an ELF Section object. Object generated by ELFObject."""
|
||||||
# Python3 way
|
# Python3 way
|
||||||
# super().__init__('pe-section')
|
# super().__init__('pe-section')
|
||||||
super(ELFSectionObject, self).__init__('elf-section', **kwargs)
|
super().__init__('elf-section', **kwargs)
|
||||||
self.__section = section
|
self.__section = section
|
||||||
self.__data = bytes(self.__section.content)
|
self.__data = bytes(self.__section.content)
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('name', value=self.__section.name)
|
self.add_attribute('name', value=self.__section.name)
|
||||||
self.add_attribute('type', value=str(self.__section.type).split('.')[1])
|
self.add_attribute('type', value=str(self.__section.type).split('.')[1])
|
||||||
for flag in self.__section.flags_list:
|
for flag in self.__section.flags_list:
|
||||||
self.add_attribute('flag', value=str(flag).split('.')[1])
|
self.add_attribute('flag', value=str(flag).split('.')[1])
|
||||||
size = self.add_attribute('size-in-bytes', value=self.__section.size)
|
self.add_attribute('size-in-bytes', value=self.__section.size)
|
||||||
if int(size.value) > 0:
|
if int(self.__section.size) > 0:
|
||||||
self.add_attribute('entropy', value=self.__section.entropy)
|
self.add_attribute('entropy', value=self.__section.entropy)
|
||||||
self.add_attribute('md5', value=md5(self.__data).hexdigest())
|
self.add_attribute('md5', value=md5(self.__data).hexdigest())
|
||||||
self.add_attribute('sha1', value=sha1(self.__data).hexdigest())
|
self.add_attribute('sha1', value=sha1(self.__data).hexdigest())
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -9,16 +10,17 @@ from email import policy, message_from_bytes
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union, List, Tuple, Dict, cast, Any, Optional
|
from typing import cast, Any
|
||||||
|
|
||||||
from extract_msg import openMsg
|
from extract_msg import openMsg
|
||||||
from extract_msg.msg_classes import MessageBase
|
from extract_msg.msg_classes import MessageBase
|
||||||
|
from extract_msg.attachments import AttachmentBase, SignedAttachment
|
||||||
from extract_msg.properties import FixedLengthProp
|
from extract_msg.properties import FixedLengthProp
|
||||||
from RTFDE.exceptions import MalformedEncapsulatedRtf, NotEncapsulatedRtf # type: ignore
|
from RTFDE.exceptions import MalformedEncapsulatedRtf, NotEncapsulatedRtf # type: ignore
|
||||||
from RTFDE.deencapsulate import DeEncapsulator # type: ignore
|
from RTFDE.deencapsulate import DeEncapsulator # type: ignore
|
||||||
from oletools.common.codepages import codepage2codec # type: ignore
|
from oletools.common.codepages import codepage2codec # type: ignore
|
||||||
|
|
||||||
from ..exceptions import InvalidMISPObject, PyMISPNotImplementedYet, MISPObjectException, NewAttributeError
|
from ..exceptions import InvalidMISPObject, MISPObjectException, NewAttributeError
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
@ -29,15 +31,14 @@ class MISPMsgConverstionError(MISPObjectException):
|
||||||
|
|
||||||
|
|
||||||
class EMailObject(AbstractMISPObjectGenerator):
|
class EMailObject(AbstractMISPObjectGenerator):
|
||||||
def __init__(self, filepath: Optional[Union[Path, str]]=None, pseudofile: Optional[BytesIO]=None,
|
def __init__(self, filepath: Path | str | None=None, pseudofile: BytesIO | bytes | None=None, # type: ignore[no-untyped-def]
|
||||||
attach_original_email: bool = True, **kwargs):
|
attach_original_email: bool = True, **kwargs) -> None:
|
||||||
super().__init__('email', **kwargs)
|
super().__init__('email', **kwargs)
|
||||||
|
|
||||||
self.attach_original_email = attach_original_email
|
self.attach_original_email = attach_original_email
|
||||||
self.encapsulated_body: Union[str, None] = None
|
self.encapsulated_body: str | None = None
|
||||||
self.eml_from_msg: Union[bool, None] = None
|
self.eml_from_msg: bool | None = None
|
||||||
self.raw_emails: Dict[str, Union[BytesIO, None]] = {'msg': None,
|
self.raw_emails: dict[str, BytesIO | None] = {'msg': None, 'eml': None}
|
||||||
'eml': None}
|
|
||||||
|
|
||||||
self.__pseudofile = self.create_pseudofile(filepath, pseudofile)
|
self.__pseudofile = self.create_pseudofile(filepath, pseudofile)
|
||||||
self.email = self.parse_email()
|
self.email = self.parse_email()
|
||||||
|
|
@ -66,7 +67,7 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
return message
|
return message
|
||||||
except ValueError as _e: # Exception
|
except ValueError as _e: # Exception
|
||||||
logger.debug("Email not in .msg format or is a corrupted .msg. Attempting to decode email from other formats.")
|
logger.debug("Email not in .msg format or is a corrupted .msg. Attempting to decode email from other formats.")
|
||||||
logger.debug("Error: {} ".format(_e))
|
logger.debug(f"Error: {_e} ")
|
||||||
try:
|
try:
|
||||||
if content_in_bytes[:3] == b'\xef\xbb\xbf': # utf-8-sig byte-order mark (BOM)
|
if content_in_bytes[:3] == b'\xef\xbb\xbf': # utf-8-sig byte-order mark (BOM)
|
||||||
eml_bytes = content_in_bytes.decode("utf_8_sig").encode("utf-8")
|
eml_bytes = content_in_bytes.decode("utf_8_sig").encode("utf-8")
|
||||||
|
|
@ -78,11 +79,11 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
return eml
|
return eml
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
raise PyMISPNotImplementedYet("EmailObject does not know how to decode data passed to it. Object may not be an email. If this is an email please submit it as an issue to PyMISP so we can add support.")
|
raise InvalidMISPObject("EmailObject does not know how to decode data passed to it. Object may not be an email. If this is an email please submit it as an issue to PyMISP so we can add support.")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_pseudofile(filepath: Optional[Union[Path, str]] = None,
|
def create_pseudofile(filepath: Path | str | None = None,
|
||||||
pseudofile: Optional[BytesIO] = None) -> BytesIO:
|
pseudofile: BytesIO | bytes | None = None) -> BytesIO:
|
||||||
"""Creates a pseudofile using directly passed data or data loaded from file path.
|
"""Creates a pseudofile using directly passed data or data loaded from file path.
|
||||||
"""
|
"""
|
||||||
if filepath:
|
if filepath:
|
||||||
|
|
@ -90,6 +91,8 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
return BytesIO(f.read())
|
return BytesIO(f.read())
|
||||||
elif pseudofile and isinstance(pseudofile, BytesIO):
|
elif pseudofile and isinstance(pseudofile, BytesIO):
|
||||||
return pseudofile
|
return pseudofile
|
||||||
|
elif pseudofile and isinstance(pseudofile, bytes):
|
||||||
|
return BytesIO(pseudofile)
|
||||||
else:
|
else:
|
||||||
raise InvalidMISPObject('File buffer (BytesIO) or a path is required.')
|
raise InvalidMISPObject('File buffer (BytesIO) or a path is required.')
|
||||||
|
|
||||||
|
|
@ -102,7 +105,7 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
eml = self._build_eml(message, body, attachments)
|
eml = self._build_eml(message, body, attachments)
|
||||||
return eml
|
return eml
|
||||||
|
|
||||||
def _extract_msg_objects(self, msg_obj: MessageBase) -> Tuple[EmailMessage, Dict, List[Any]]:
|
def _extract_msg_objects(self, msg_obj: MessageBase) -> tuple[EmailMessage, dict[str, Any], list[AttachmentBase] | list[SignedAttachment]]:
|
||||||
"""Extracts email objects needed to construct an eml from a msg."""
|
"""Extracts email objects needed to construct an eml from a msg."""
|
||||||
message: EmailMessage = email.message_from_string(msg_obj.header.as_string(), policy=policy.default) # type: ignore
|
message: EmailMessage = email.message_from_string(msg_obj.header.as_string(), policy=policy.default) # type: ignore
|
||||||
body = {}
|
body = {}
|
||||||
|
|
@ -150,17 +153,16 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
attachments = msg_obj.attachments
|
attachments = msg_obj.attachments
|
||||||
return message, body, attachments
|
return message, body, attachments
|
||||||
|
|
||||||
def _build_eml(self, message: EmailMessage, body: dict, attachments: list) -> EmailMessage:
|
def _build_eml(self, message: EmailMessage, body: dict[str, Any], attachments: list[Any]) -> EmailMessage:
|
||||||
"""Constructs an eml file from objects extracted from a msg."""
|
"""Constructs an eml file from objects extracted from a msg."""
|
||||||
# Order the body objects by increasing complexity and toss any missing objects
|
# Order the body objects by increasing complexity and toss any missing objects
|
||||||
body_objects: List[dict] = [body.get('text', {}),
|
body_objects: list[dict[str, Any]] = [i for i in [body.get('text'),
|
||||||
body.get('html', {}),
|
body.get('html'),
|
||||||
body.get('rtf', {})]
|
body.get('rtf')] if i is not None]
|
||||||
body_objects = [i for i in body_objects if i != {}]
|
|
||||||
# If this a non-multipart email then we only need to attach the payload
|
# If this a non-multipart email then we only need to attach the payload
|
||||||
if message.get_content_maintype() != 'multipart':
|
if message.get_content_maintype() != 'multipart':
|
||||||
for _body in body_objects:
|
for _body in body_objects:
|
||||||
if "text/{0}".format(_body['subtype']) == message.get_content_type():
|
if "text/{}".format(_body['subtype']) == message.get_content_type():
|
||||||
message.set_content(**_body)
|
message.set_content(**_body)
|
||||||
return message
|
return message
|
||||||
raise MISPMsgConverstionError("Unable to find appropriate eml payload in message body.")
|
raise MISPMsgConverstionError("Unable to find appropriate eml payload in message body.")
|
||||||
|
|
@ -172,8 +174,8 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
if isinstance(body.get('html', None), dict):
|
if isinstance(body.get('html', None), dict):
|
||||||
_html = body.get('html', {}).get('obj')
|
_html = body.get('html', {}).get('obj')
|
||||||
for attch in attachments:
|
for attch in attachments:
|
||||||
if _html.find("cid:{0}".format(attch.cid)) != -1:
|
if _html.find(f"cid:{attch.cid}") != -1:
|
||||||
_content_type = attch._getStringStream('__substg1.0_370E')
|
_content_type = attch.getStringStream('__substg1.0_370E')
|
||||||
maintype, subtype = _content_type.split("/", 1)
|
maintype, subtype = _content_type.split("/", 1)
|
||||||
related_content[attch.cid] = (attch,
|
related_content[attch.cid] = (attch,
|
||||||
{'obj': attch.data,
|
{'obj': attch.data,
|
||||||
|
|
@ -210,7 +212,7 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
message.add_alternative(**mime_dict)
|
message.add_alternative(**mime_dict)
|
||||||
for attch in attachments: # Add attachments at the end.
|
for attch in attachments: # Add attachments at the end.
|
||||||
if attch.cid not in related_content.keys():
|
if attch.cid not in related_content.keys():
|
||||||
_content_type = attch._getStringStream('__substg1.0_370E')
|
_content_type = attch.getStringStream('__substg1.0_370E')
|
||||||
maintype, subtype = _content_type.split("/", 1)
|
maintype, subtype = _content_type.split("/", 1)
|
||||||
message.add_attachment(attch.data,
|
message.add_attachment(attch.data,
|
||||||
maintype=maintype,
|
maintype=maintype,
|
||||||
|
|
@ -224,7 +226,7 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
return message
|
return message
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _update_content_disp_properties(msg_attch, eml_attch):
|
def _update_content_disp_properties(msg_attch: AttachmentBase, eml_attch: EmailMessage) -> None:
|
||||||
"""Set Content-Disposition params on binary eml objects
|
"""Set Content-Disposition params on binary eml objects
|
||||||
|
|
||||||
You currently have to set non-filename content-disp params by hand in python.
|
You currently have to set non-filename content-disp params by hand in python.
|
||||||
|
|
@ -234,14 +236,14 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
for num, name in attch_cont_disp_props.items():
|
for num, name in attch_cont_disp_props.items():
|
||||||
try:
|
try:
|
||||||
eml_attch.set_param(name,
|
eml_attch.set_param(name,
|
||||||
email.utils.format_datetime(msg_attch.props[num].value),
|
email.utils.format_datetime(msg_attch.props.getValue(num)),
|
||||||
header='Content-Disposition')
|
header='Content-Disposition')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# It's fine if they don't have those values
|
# It's fine if they don't have those values
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attachments(self) -> List[Tuple[Optional[str], BytesIO]]:
|
def attachments(self) -> list[tuple[str | None, BytesIO]]:
|
||||||
to_return = []
|
to_return = []
|
||||||
try:
|
try:
|
||||||
for attachment in self.email.iter_attachments():
|
for attachment in self.email.iter_attachments():
|
||||||
|
|
@ -255,7 +257,7 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
pass
|
pass
|
||||||
return to_return
|
return to_return
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
|
|
||||||
# Attach original & Converted
|
# Attach original & Converted
|
||||||
if self.attach_original_email is not None:
|
if self.attach_original_email is not None:
|
||||||
|
|
@ -267,21 +269,30 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
data=self.raw_emails.get('msg'))
|
data=self.raw_emails.get('msg'))
|
||||||
|
|
||||||
message = self.email
|
message = self.email
|
||||||
|
body: EmailMessage
|
||||||
|
|
||||||
for _pref, body in message._find_body(message, preferencelist=['plain', 'html']):
|
if body := message.get_body(preferencelist=['plain']):
|
||||||
comment = "{0} body".format(body.get_content_type())
|
comment = f"{body.get_content_type()} body"
|
||||||
if self.encapsulated_body == body.get_content_type():
|
if self.encapsulated_body == body.get_content_type():
|
||||||
comment += " De-Encapsulated from RTF in original msg."
|
comment += " De-Encapsulated from RTF in original msg."
|
||||||
self.add_attribute("email-body",
|
self.add_attribute("email-body",
|
||||||
body.get_content(),
|
body.get_content(),
|
||||||
comment=comment)
|
comment=comment)
|
||||||
|
|
||||||
headers = ["{}: {}".format(k, v) for k, v in message.items()]
|
if body := message.get_body(preferencelist=['html']):
|
||||||
|
comment = f"{body.get_content_type()} body"
|
||||||
|
if self.encapsulated_body == body.get_content_type():
|
||||||
|
comment += " De-Encapsulated from RTF in original msg."
|
||||||
|
self.add_attribute("email-body",
|
||||||
|
body.get_content(),
|
||||||
|
comment=comment)
|
||||||
|
|
||||||
|
headers = [f"{k}: {v}" for k, v in message.items()]
|
||||||
if headers:
|
if headers:
|
||||||
self.add_attribute("header", "\n".join(headers))
|
self.add_attribute("header", "\n".join(headers))
|
||||||
|
|
||||||
if "Date" in message and message.get('date').datetime is not None:
|
if "Date" in message and message['date'].datetime is not None:
|
||||||
self.add_attribute("send-date", message.get('date').datetime)
|
self.add_attribute("send-date", message['date'].datetime)
|
||||||
|
|
||||||
if "To" in message:
|
if "To" in message:
|
||||||
self.__add_emails("to", message["To"])
|
self.__add_emails("to", message["To"])
|
||||||
|
|
@ -325,31 +336,32 @@ class EMailObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
self.__generate_received()
|
self.__generate_received()
|
||||||
|
|
||||||
def __add_emails(self, typ: str, data: str, insert_display_names: bool = True):
|
def __add_emails(self, typ: str, data: str, insert_display_names: bool = True) -> None:
|
||||||
addresses = []
|
addresses: list[dict[str, str]] = []
|
||||||
display_names = []
|
display_names: list[dict[str, str]] = []
|
||||||
|
|
||||||
for realname, address in email.utils.getaddresses([data]):
|
for realname, address in email.utils.getaddresses([data]):
|
||||||
if address and realname:
|
if address and realname:
|
||||||
addresses.append({"value": address, "comment": "{} <{}>".format(realname, address)})
|
addresses.append({"value": address, "comment": f"{realname} <{address}>"})
|
||||||
elif address:
|
elif address:
|
||||||
addresses.append({"value": address})
|
addresses.append({"value": address})
|
||||||
else: # parsing failed, skip
|
else: # parsing failed, skip
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if realname:
|
if realname:
|
||||||
display_names.append({"value": realname, "comment": "{} <{}>".format(realname, address)})
|
display_names.append({"value": realname, "comment": f"{realname} <{address}>"})
|
||||||
|
|
||||||
if addresses:
|
for a in addresses:
|
||||||
self.add_attributes(typ, *addresses)
|
self.add_attribute(typ, **a)
|
||||||
if insert_display_names and display_names:
|
if insert_display_names and display_names:
|
||||||
try:
|
try:
|
||||||
self.add_attributes("{}-display-name".format(typ), *display_names)
|
for d in display_names:
|
||||||
|
self.add_attribute(f"{typ}-display-name", **d)
|
||||||
except NewAttributeError:
|
except NewAttributeError:
|
||||||
# email object doesn't support display name for all email addrs
|
# email object doesn't support display name for all email addrs
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __generate_received(self):
|
def __generate_received(self) -> None:
|
||||||
"""
|
"""
|
||||||
Extract IP addresses from received headers that are not private. Also extract hostnames or domains.
|
Extract IP addresses from received headers that are not private. Also extract hostnames or domains.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pymispgalaxies import Clusters # type: ignore
|
from pymispgalaxies import Clusters # type: ignore
|
||||||
|
|
@ -14,7 +15,7 @@ except ImportError:
|
||||||
has_pymispgalaxies = False
|
has_pymispgalaxies = False
|
||||||
|
|
||||||
|
|
||||||
def revert_tag_from_galaxies(tag):
|
def revert_tag_from_galaxies(tag: str) -> list[str]:
|
||||||
clusters = Clusters()
|
clusters = Clusters()
|
||||||
try:
|
try:
|
||||||
return clusters.revert_machinetag(tag)
|
return clusters.revert_machinetag(tag)
|
||||||
|
|
@ -22,7 +23,7 @@ def revert_tag_from_galaxies(tag):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def revert_tag_from_taxonomies(tag):
|
def revert_tag_from_taxonomies(tag: str) -> list[str]:
|
||||||
taxonomies = Taxonomies()
|
taxonomies = Taxonomies()
|
||||||
try:
|
try:
|
||||||
return taxonomies.revert_machinetag(tag)
|
return taxonomies.revert_machinetag(tag)
|
||||||
|
|
@ -30,7 +31,7 @@ def revert_tag_from_taxonomies(tag):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def search_taxonomies(query):
|
def search_taxonomies(query: str) -> list[str]:
|
||||||
taxonomies = Taxonomies()
|
taxonomies = Taxonomies()
|
||||||
found = taxonomies.search(query)
|
found = taxonomies.search(query)
|
||||||
if not found:
|
if not found:
|
||||||
|
|
@ -38,6 +39,6 @@ def search_taxonomies(query):
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
def search_galaxies(query):
|
def search_galaxies(query: str) -> list[str]:
|
||||||
clusters = Clusters()
|
clusters = Clusters()
|
||||||
return clusters.search(query)
|
return clusters.search(query)
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
class Fail2BanObject(AbstractMISPObjectGenerator):
|
class Fail2BanObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parameters: dict, strict: bool = True, **kwargs):
|
def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs): # type: ignore[no-untyped-def]
|
||||||
super().__init__('fail2ban', strict=strict, **kwargs)
|
super().__init__('fail2ban', strict=strict, **kwargs)
|
||||||
self._parameters = parameters
|
self._parameters = parameters
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
timestamp = self._sanitize_timestamp(self._parameters.pop('processing-timestamp', None))
|
timestamp = self._sanitize_timestamp(self._parameters.pop('processing-timestamp', None))
|
||||||
self._parameters['processing-timestamp'] = timestamp
|
self._parameters['processing-timestamp'] = timestamp
|
||||||
super().generate_attributes()
|
super().generate_attributes()
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pymisp import MISPEvent
|
from pymisp import MISPEvent
|
||||||
import json
|
import json
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
def feed_meta_generator(path: Path):
|
def feed_meta_generator(path: Path) -> None:
|
||||||
manifests = {}
|
manifests = {}
|
||||||
hashes: List[str] = []
|
hashes: list[str] = []
|
||||||
|
|
||||||
for f_name in path.glob('*.json'):
|
for f_name in path.glob('*.json'):
|
||||||
if str(f_name.name) == 'manifest.json':
|
if str(f_name.name) == 'manifest.json':
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from ..exceptions import InvalidMISPObject
|
from ..exceptions import InvalidMISPObject
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
|
|
@ -10,7 +11,6 @@ import math
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union, Optional
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ except ImportError:
|
||||||
HAS_PYDEEP = False
|
HAS_PYDEEP = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import magic # type: ignore
|
import magic
|
||||||
HAS_MAGIC = True
|
HAS_MAGIC = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_MAGIC = False
|
HAS_MAGIC = False
|
||||||
|
|
@ -30,7 +30,9 @@ except ImportError:
|
||||||
|
|
||||||
class FileObject(AbstractMISPObjectGenerator):
|
class FileObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, filepath: Optional[Union[Path, str]] = None, pseudofile: Optional[BytesIO] = None, filename: Optional[str] = None, **kwargs) -> None:
|
def __init__(self, filepath: Path | str | None = None, # type: ignore[no-untyped-def]
|
||||||
|
pseudofile: BytesIO | bytes | None = None,
|
||||||
|
filename: str | None = None, **kwargs) -> None:
|
||||||
super().__init__('file', **kwargs)
|
super().__init__('file', **kwargs)
|
||||||
if not HAS_PYDEEP:
|
if not HAS_PYDEEP:
|
||||||
logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]")
|
logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]")
|
||||||
|
|
@ -55,10 +57,10 @@ class FileObject(AbstractMISPObjectGenerator):
|
||||||
self.__data = self.__pseudofile.getvalue()
|
self.__data = self.__pseudofile.getvalue()
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('filename', value=self.__filename)
|
self.add_attribute('filename', value=self.__filename)
|
||||||
size = self.add_attribute('size-in-bytes', value=len(self.__data))
|
self.add_attribute('size-in-bytes', value=len(self.__data))
|
||||||
if int(size.value) > 0:
|
if len(self.__data) > 0:
|
||||||
self.add_attribute('entropy', value=self.__entropy_H(self.__data))
|
self.add_attribute('entropy', value=self.__entropy_H(self.__data))
|
||||||
self.add_attribute('md5', value=md5(self.__data).hexdigest())
|
self.add_attribute('md5', value=md5(self.__data).hexdigest())
|
||||||
self.add_attribute('sha1', value=sha1(self.__data).hexdigest())
|
self.add_attribute('sha1', value=sha1(self.__data).hexdigest())
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
class GenericObjectGenerator(AbstractMISPObjectGenerator):
|
class GenericObjectGenerator(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
# FIXME: this method is different from the master one, and that's probably not a good idea.
|
# FIXME: this method is different from the master one, and that's probably not a good idea.
|
||||||
def generate_attributes(self, attributes: List[dict]): # type: ignore
|
def generate_attributes(self, attributes: list[dict[str, Any]]) -> None: # type: ignore[override]
|
||||||
"""Generates MISPObjectAttributes from a list of dictionaries.
|
"""Generates MISPObjectAttributes from a list of dictionaries.
|
||||||
Each entry if the list must be in one of the two following formats:
|
Each entry if the list must be in one of the two following formats:
|
||||||
* {<object_relation>: <value>}
|
* {<object_relation>: <value>}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
class GeolocationObject(AbstractMISPObjectGenerator):
|
class GeolocationObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parameters: dict, strict: bool = True, **kwargs):
|
def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('geolocation', strict=strict, **kwargs)
|
super().__init__('geolocation', strict=strict, **kwargs)
|
||||||
self._parameters = parameters
|
self._parameters = parameters
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
first = self._sanitize_timestamp(self._parameters.pop('first-seen', None))
|
first = self._sanitize_timestamp(self._parameters.pop('first-seen', None))
|
||||||
self._parameters['first-seen'] = first
|
self._parameters['first-seen'] = first
|
||||||
last = self._sanitize_timestamp(self._parameters.pop('last-seen', None))
|
last = self._sanitize_timestamp(self._parameters.pop('last-seen', None))
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,24 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
class GitVulnFinderObject(AbstractMISPObjectGenerator):
|
class GitVulnFinderObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parameters: dict, strict: bool = True, **kwargs):
|
def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('git-vuln-finder', strict=strict, **kwargs)
|
super().__init__('git-vuln-finder', strict=strict, **kwargs)
|
||||||
self._parameters = parameters
|
self._parameters = parameters
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
authored_date = self._sanitize_timestamp(self._parameters.pop('authored_date', None))
|
authored_date = self._sanitize_timestamp(self._parameters.pop('authored_date', None))
|
||||||
self._parameters['authored_date'] = authored_date
|
self._parameters['authored_date'] = authored_date
|
||||||
committed_date = self._sanitize_timestamp(self._parameters.pop('committed_date', None))
|
committed_date = self._sanitize_timestamp(self._parameters.pop('committed_date', None))
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,30 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ..api import PyMISP
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pymispwarninglists import WarningLists # type: ignore
|
from pymispwarninglists import WarningLists, WarningList # type: ignore
|
||||||
has_pymispwarninglists = True
|
has_pymispwarninglists = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
has_pymispwarninglists = False
|
has_pymispwarninglists = False
|
||||||
|
|
||||||
|
|
||||||
def from_instance(pymisp_instance, slow_search=False):
|
def from_instance(pymisp_instance: PyMISP, slow_search: bool=False) -> WarningLists:
|
||||||
"""Load the warnindlist from an existing MISP instance
|
"""Load the warnindlist from an existing MISP instance
|
||||||
:pymisp_instance: Already instantialized PyMISP instance."""
|
:pymisp_instance: Already instantialized PyMISP instance."""
|
||||||
|
|
||||||
warninglists_index = pymisp_instance.get_warninglists()['Warninglists']
|
warninglists_index = pymisp_instance.warninglists(pythonify=True)
|
||||||
all_warningslists = []
|
all_warningslists = []
|
||||||
for warninglist in warninglists_index:
|
for warninglist in warninglists_index:
|
||||||
wl = pymisp_instance.get_warninglist(warninglist['Warninglist']['id'])['Warninglist']
|
if isinstance(warninglist, WarningList):
|
||||||
wl['list'] = wl.pop('WarninglistEntry')
|
wl = pymisp_instance.get_warninglist(warninglist['Warninglist']['id'])['Warninglist']
|
||||||
all_warningslists.append(wl)
|
wl['list'] = wl.pop('WarninglistEntry')
|
||||||
|
all_warningslists.append(wl)
|
||||||
|
|
||||||
return WarningLists(slow_search, all_warningslists)
|
return WarningLists(slow_search, all_warningslists)
|
||||||
|
|
||||||
|
|
||||||
def from_package(slow_search=False):
|
def from_package(slow_search: bool=False) -> WarningLists:
|
||||||
return WarningLists(slow_search)
|
return WarningLists(slow_search)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from hashlib import md5, sha1, sha256, sha512
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from ..exceptions import InvalidMISPObject
|
from ..exceptions import InvalidMISPObject
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
|
||||||
from io import BytesIO
|
|
||||||
from hashlib import md5, sha1, sha256, sha512
|
|
||||||
import logging
|
|
||||||
from typing import Optional, Union
|
|
||||||
from pathlib import Path
|
|
||||||
from . import FileObject
|
from . import FileObject
|
||||||
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
|
|
||||||
import lief
|
import lief
|
||||||
|
|
||||||
|
|
@ -21,7 +25,10 @@ except ImportError:
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
def make_macho_objects(lief_parsed: lief.MachO.Binary, misp_file: FileObject, standalone: bool = True, default_attributes_parameters: dict = {}):
|
def make_macho_objects(lief_parsed: lief.MachO.Binary,
|
||||||
|
misp_file: FileObject,
|
||||||
|
standalone: bool = True,
|
||||||
|
default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, MachOObject, list[MachOSectionObject]]:
|
||||||
macho_object = MachOObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters)
|
macho_object = MachOObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters)
|
||||||
misp_file.add_reference(macho_object.uuid, 'includes', 'MachO indicators')
|
misp_file.add_reference(macho_object.uuid, 'includes', 'MachO indicators')
|
||||||
macho_sections = []
|
macho_sections = []
|
||||||
|
|
@ -32,31 +39,43 @@ def make_macho_objects(lief_parsed: lief.MachO.Binary, misp_file: FileObject, st
|
||||||
|
|
||||||
class MachOObject(AbstractMISPObjectGenerator):
|
class MachOObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parsed: Optional[lief.MachO.Binary] = None, filepath: Optional[Union[Path, str]] = None, pseudofile: Optional[BytesIO] = None, **kwargs):
|
__macho: lief.MachO.Binary
|
||||||
|
|
||||||
|
def __init__(self, parsed: lief.MachO.Binary | lief.MachO.FatBinary | None = None, # type: ignore[no-untyped-def]
|
||||||
|
filepath: Path | str | None = None,
|
||||||
|
pseudofile: BytesIO | list[int] | None = None,
|
||||||
|
**kwargs) -> None:
|
||||||
"""Creates an MachO object, with lief"""
|
"""Creates an MachO object, with lief"""
|
||||||
super().__init__('macho', **kwargs)
|
super().__init__('macho', **kwargs)
|
||||||
if not HAS_PYDEEP:
|
if not HAS_PYDEEP:
|
||||||
logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]")
|
logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]")
|
||||||
if pseudofile:
|
if pseudofile:
|
||||||
if isinstance(pseudofile, BytesIO):
|
if isinstance(pseudofile, BytesIO):
|
||||||
self.__macho = lief.MachO.parse(io=pseudofile)
|
m = lief.MachO.parse(obj=pseudofile)
|
||||||
elif isinstance(pseudofile, bytes):
|
elif isinstance(pseudofile, bytes):
|
||||||
self.__macho = lief.MachO.parse(raw=pseudofile)
|
m = lief.MachO.parse(raw=list(pseudofile))
|
||||||
|
elif isinstance(pseudofile, list):
|
||||||
|
m = lief.MachO.parse(raw=pseudofile)
|
||||||
else:
|
else:
|
||||||
raise InvalidMISPObject('Pseudo file can be BytesIO or bytes got {}'.format(type(pseudofile)))
|
raise InvalidMISPObject(f'Pseudo file can be BytesIO or bytes got {type(pseudofile)}')
|
||||||
|
if not m:
|
||||||
|
raise InvalidMISPObject('Unable to parse pseudofile')
|
||||||
|
self.__macho = m.at(0)
|
||||||
elif filepath:
|
elif filepath:
|
||||||
self.__macho = lief.MachO.parse(filepath)
|
if m := lief.MachO.parse(filepath):
|
||||||
|
self.__macho = m.at(0)
|
||||||
elif parsed:
|
elif parsed:
|
||||||
# Got an already parsed blob
|
# Got an already parsed blob
|
||||||
if isinstance(parsed, lief.MachO.Binary):
|
if isinstance(parsed, lief.MachO.FatBinary):
|
||||||
|
self.__macho = parsed.at(0)
|
||||||
|
elif isinstance(parsed, lief.MachO.Binary):
|
||||||
self.__macho = parsed
|
self.__macho = parsed
|
||||||
else:
|
else:
|
||||||
raise InvalidMISPObject('Not a lief.MachO.Binary: {}'.format(type(parsed)))
|
raise InvalidMISPObject(f'Not a lief.MachO.Binary: {type(parsed)}')
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('type', value=str(self.__macho.header.file_type).split('.')[1])
|
self.add_attribute('type', value=str(self.__macho.header.file_type).split('.')[1])
|
||||||
self.add_attribute('name', value=self.__macho.name)
|
|
||||||
# General information
|
# General information
|
||||||
if self.__macho.has_entrypoint:
|
if self.__macho.has_entrypoint:
|
||||||
self.add_attribute('entrypoint-address', value=self.__macho.entrypoint)
|
self.add_attribute('entrypoint-address', value=self.__macho.entrypoint)
|
||||||
|
|
@ -66,7 +85,7 @@ class MachOObject(AbstractMISPObjectGenerator):
|
||||||
pos = 0
|
pos = 0
|
||||||
for section in self.__macho.sections:
|
for section in self.__macho.sections:
|
||||||
s = MachOSectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters)
|
s = MachOSectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters)
|
||||||
self.add_reference(s.uuid, 'includes', 'Section {} of MachO'.format(pos))
|
self.add_reference(s.uuid, 'includes', f'Section {pos} of MachO')
|
||||||
pos += 1
|
pos += 1
|
||||||
self.sections.append(s)
|
self.sections.append(s)
|
||||||
self.add_attribute('number-sections', value=len(self.sections))
|
self.add_attribute('number-sections', value=len(self.sections))
|
||||||
|
|
@ -74,19 +93,19 @@ class MachOObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
class MachOSectionObject(AbstractMISPObjectGenerator):
|
class MachOSectionObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, section: lief.MachO.Section, **kwargs):
|
def __init__(self, section: lief.MachO.Section, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
"""Creates an MachO Section object. Object generated by MachOObject."""
|
"""Creates an MachO Section object. Object generated by MachOObject."""
|
||||||
# Python3 way
|
# Python3 way
|
||||||
# super().__init__('pe-section')
|
# super().__init__('pe-section')
|
||||||
super(MachOSectionObject, self).__init__('macho-section', **kwargs)
|
super().__init__('macho-section', **kwargs)
|
||||||
self.__section = section
|
self.__section = section
|
||||||
self.__data = bytes(self.__section.content)
|
self.__data = bytes(self.__section.content)
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('name', value=self.__section.name)
|
self.add_attribute('name', value=self.__section.name)
|
||||||
size = self.add_attribute('size-in-bytes', value=self.__section.size)
|
self.add_attribute('size-in-bytes', value=self.__section.size)
|
||||||
if int(size.value) > 0:
|
if int(self.__section.size) > 0:
|
||||||
self.add_attribute('entropy', value=self.__section.entropy)
|
self.add_attribute('entropy', value=self.__section.entropy)
|
||||||
self.add_attribute('md5', value=md5(self.__data).hexdigest())
|
self.add_attribute('md5', value=md5(self.__data).hexdigest())
|
||||||
self.add_attribute('sha1', value=sha1(self.__data).hexdigest())
|
self.add_attribute('sha1', value=sha1(self.__data).hexdigest())
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,24 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
# NOTE: Reference on how this module is used: https://vvx7.io/posts/2020/05/misp-slack-bot/
|
# NOTE: Reference on how this module is used: https://vvx7.io/posts/2020/05/misp-slack-bot/
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
class MicroblogObject(AbstractMISPObjectGenerator):
|
class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parameters: dict, strict: bool = True, **kwargs):
|
def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs): # type: ignore[no-untyped-def]
|
||||||
super().__init__('microblog', strict=strict, **kwargs)
|
super().__init__('microblog', strict=strict, **kwargs)
|
||||||
self._parameters = parameters
|
self._parameters = parameters
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
# Raw post.
|
# Raw post.
|
||||||
if 'post' in self._parameters:
|
if 'post' in self._parameters:
|
||||||
self.add_attribute('post', value=self._parameters['post'])
|
self.add_attribute('post', value=self._parameters['post'])
|
||||||
|
|
@ -32,7 +34,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
# Original URL location of the microblog post (potentially malicious.
|
# Original URL location of the microblog post (potentially malicious.
|
||||||
if 'url' in self._parameters:
|
if 'url' in self._parameters:
|
||||||
if isinstance(self._parameters.get('url'), list):
|
if isinstance(self._parameters.get('url'), list):
|
||||||
for i in self._parameters.get('url'):
|
for i in self._parameters['url']:
|
||||||
self.add_attribute('url', value=i)
|
self.add_attribute('url', value=i)
|
||||||
else:
|
else:
|
||||||
self.add_attribute('url', value=self._parameters['url'])
|
self.add_attribute('url', value=self._parameters['url'])
|
||||||
|
|
@ -40,7 +42,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
# Archive of the original document (Internet Archive, Archive.is, etc).
|
# Archive of the original document (Internet Archive, Archive.is, etc).
|
||||||
if 'archive' in self._parameters:
|
if 'archive' in self._parameters:
|
||||||
if isinstance(self._parameters.get('archive'), list):
|
if isinstance(self._parameters.get('archive'), list):
|
||||||
for i in self._parameters.get('archive'):
|
for i in self._parameters['archive']:
|
||||||
self.add_attribute('archive', value=i)
|
self.add_attribute('archive', value=i)
|
||||||
else:
|
else:
|
||||||
self.add_attribute('archive', value=self._parameters['archive'])
|
self.add_attribute('archive', value=self._parameters['archive'])
|
||||||
|
|
@ -74,7 +76,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
"Instagram", "Forum", "Other"]
|
"Instagram", "Forum", "Other"]
|
||||||
if 'type' in self._parameters:
|
if 'type' in self._parameters:
|
||||||
if isinstance(self._parameters.get('type'), list):
|
if isinstance(self._parameters.get('type'), list):
|
||||||
for i in self._parameters.get('type'):
|
for i in self._parameters['type']:
|
||||||
if i in type_allowed_values:
|
if i in type_allowed_values:
|
||||||
self.add_attribute('type', value=i)
|
self.add_attribute('type', value=i)
|
||||||
else:
|
else:
|
||||||
|
|
@ -85,7 +87,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
type_allowed_values = ["Informative", "Malicious", "Misinformation", "Disinformation", "Unknown"]
|
type_allowed_values = ["Informative", "Malicious", "Misinformation", "Disinformation", "Unknown"]
|
||||||
if 'state' in self._parameters:
|
if 'state' in self._parameters:
|
||||||
if isinstance(self._parameters.get('state'), list):
|
if isinstance(self._parameters.get('state'), list):
|
||||||
for i in self._parameters.get('state'):
|
for i in self._parameters['state']:
|
||||||
if i in type_allowed_values:
|
if i in type_allowed_values:
|
||||||
self.add_attribute('state', value=i)
|
self.add_attribute('state', value=i)
|
||||||
else:
|
else:
|
||||||
|
|
@ -100,7 +102,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
type_allowed_values = ["Verified", "Unverified", "Unknown"]
|
type_allowed_values = ["Verified", "Unverified", "Unknown"]
|
||||||
if 'verified-username' in self._parameters:
|
if 'verified-username' in self._parameters:
|
||||||
if isinstance(self._parameters.get('verified-username'), list):
|
if isinstance(self._parameters.get('verified-username'), list):
|
||||||
for i in self._parameters.get('verified-username'):
|
for i in self._parameters['verified-username']:
|
||||||
if i in type_allowed_values:
|
if i in type_allowed_values:
|
||||||
self.add_attribute('verified-username', value=i)
|
self.add_attribute('verified-username', value=i)
|
||||||
else:
|
else:
|
||||||
|
|
@ -110,7 +112,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
# embedded-link.
|
# embedded-link.
|
||||||
if 'embedded-link' in self._parameters:
|
if 'embedded-link' in self._parameters:
|
||||||
if isinstance(self._parameters.get('embedded-link'), list):
|
if isinstance(self._parameters.get('embedded-link'), list):
|
||||||
for i in self._parameters.get('embedded-link'):
|
for i in self._parameters['embedded-link']:
|
||||||
self.add_attribute('embedded-link', value=i)
|
self.add_attribute('embedded-link', value=i)
|
||||||
else:
|
else:
|
||||||
self.add_attribute('embedded-link', value=self._parameters['embedded-link'])
|
self.add_attribute('embedded-link', value=self._parameters['embedded-link'])
|
||||||
|
|
@ -118,7 +120,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
# embedded-safe-link
|
# embedded-safe-link
|
||||||
if 'embedded-safe-link' in self._parameters:
|
if 'embedded-safe-link' in self._parameters:
|
||||||
if isinstance(self._parameters.get('embedded-safe-link'), list):
|
if isinstance(self._parameters.get('embedded-safe-link'), list):
|
||||||
for i in self._parameters.get('embedded-safe-link'):
|
for i in self._parameters['embedded-safe-link']:
|
||||||
self.add_attribute('embedded-safe-link', value=i)
|
self.add_attribute('embedded-safe-link', value=i)
|
||||||
else:
|
else:
|
||||||
self.add_attribute('embedded-safe-link', value=self._parameters['embedded-safe-link'])
|
self.add_attribute('embedded-safe-link', value=self._parameters['embedded-safe-link'])
|
||||||
|
|
@ -126,7 +128,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
# Hashtag into the microblog post.
|
# Hashtag into the microblog post.
|
||||||
if 'hashtag' in self._parameters:
|
if 'hashtag' in self._parameters:
|
||||||
if isinstance(self._parameters.get('hashtag'), list):
|
if isinstance(self._parameters.get('hashtag'), list):
|
||||||
for i in self._parameters.get('hashtag'):
|
for i in self._parameters['hashtag']:
|
||||||
self.add_attribute('hashtag', value=i)
|
self.add_attribute('hashtag', value=i)
|
||||||
else:
|
else:
|
||||||
self.add_attribute('hashtag', value=self._parameters['hashtag'])
|
self.add_attribute('hashtag', value=self._parameters['hashtag'])
|
||||||
|
|
@ -134,7 +136,7 @@ class MicroblogObject(AbstractMISPObjectGenerator):
|
||||||
# username quoted
|
# username quoted
|
||||||
if 'username-quoted' in self._parameters:
|
if 'username-quoted' in self._parameters:
|
||||||
if isinstance(self._parameters.get('username-quoted'), list):
|
if isinstance(self._parameters.get('username-quoted'), list):
|
||||||
for i in self._parameters.get('username-quoted'):
|
for i in self._parameters['username-quoted']:
|
||||||
self.add_attribute('username-quoted', value=i)
|
self.add_attribute('username-quoted', value=i)
|
||||||
else:
|
else:
|
||||||
self.add_attribute('username-quoted', value=self._parameters['username-quoted'])
|
self.add_attribute('username-quoted', value=self._parameters['username-quoted'])
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
from __future__ import annotations
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from .. import MISPEvent
|
from .. import MISPEvent
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -13,23 +14,23 @@ except ImportError:
|
||||||
|
|
||||||
class Neo4j():
|
class Neo4j():
|
||||||
|
|
||||||
def __init__(self, host='localhost:7474', username='neo4j', password='neo4j'):
|
def __init__(self, host: str='localhost:7474', username: str='neo4j', password: str='neo4j') -> None:
|
||||||
if not has_py2neo:
|
if not has_py2neo:
|
||||||
raise Exception('py2neo is required, please install: pip install py2neo')
|
raise Exception('py2neo is required, please install: pip install py2neo')
|
||||||
authenticate(host, username, password)
|
authenticate(host, username, password)
|
||||||
self.graph = Graph("http://{}/db/data/".format(host))
|
self.graph = Graph(f"http://{host}/db/data/")
|
||||||
|
|
||||||
def load_events_directory(self, directory):
|
def load_events_directory(self, directory: str) -> None:
|
||||||
self.events = []
|
self.events: list[MISPEvent] = []
|
||||||
for path in glob.glob(os.path.join(directory, '*.json')):
|
for path in glob.glob(os.path.join(directory, '*.json')):
|
||||||
e = MISPEvent()
|
e = MISPEvent()
|
||||||
e.load(path)
|
e.load(path)
|
||||||
self.import_event(e)
|
self.import_event(e)
|
||||||
|
|
||||||
def del_all(self):
|
def del_all(self) -> None:
|
||||||
self.graph.delete_all()
|
self.graph.delete_all()
|
||||||
|
|
||||||
def import_event(self, event):
|
def import_event(self, event: MISPEvent) -> None:
|
||||||
tx = self.graph.begin()
|
tx = self.graph.begin()
|
||||||
event_node = Node('Event', uuid=event.uuid, name=event.info)
|
event_node = Node('Event', uuid=event.uuid, name=event.info)
|
||||||
# event_node['distribution'] = event.distribution
|
# event_node['distribution'] = event.distribution
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
|
from __future__ import annotations
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
@ -156,7 +155,7 @@ def extract_field(report, field_name):
|
||||||
def load_openioc_file(openioc_path):
|
def load_openioc_file(openioc_path):
|
||||||
if not os.path.exists(openioc_path):
|
if not os.path.exists(openioc_path):
|
||||||
raise Exception("Path doesn't exists.")
|
raise Exception("Path doesn't exists.")
|
||||||
with open(openioc_path, 'r') as f:
|
with open(openioc_path) as f:
|
||||||
return load_openioc(f)
|
return load_openioc(f)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from ..exceptions import InvalidMISPObject
|
from __future__ import annotations
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
|
||||||
from io import BytesIO
|
|
||||||
from hashlib import md5, sha1, sha256, sha512
|
|
||||||
from datetime import datetime
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, Union
|
|
||||||
from pathlib import Path
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
from datetime import datetime
|
||||||
|
from hashlib import md5, sha1, sha256, sha512
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from . import FileObject
|
from . import FileObject
|
||||||
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
|
from ..exceptions import InvalidMISPObject
|
||||||
|
|
||||||
import lief
|
import lief
|
||||||
|
import lief.PE
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pydeep # type: ignore
|
import pydeep # type: ignore
|
||||||
|
|
@ -24,7 +27,10 @@ except ImportError:
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
def make_pe_objects(lief_parsed: lief.PE.Binary, misp_file: FileObject, standalone: bool = True, default_attributes_parameters: dict = {}):
|
def make_pe_objects(lief_parsed: lief.PE.Binary,
|
||||||
|
misp_file: FileObject,
|
||||||
|
standalone: bool = True,
|
||||||
|
default_attributes_parameters: dict[str, Any] = {}) -> tuple[FileObject, PEObject, list[PESectionObject]]:
|
||||||
pe_object = PEObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters)
|
pe_object = PEObject(parsed=lief_parsed, standalone=standalone, default_attributes_parameters=default_attributes_parameters)
|
||||||
misp_file.add_reference(pe_object.uuid, 'includes', 'PE indicators')
|
misp_file.add_reference(pe_object.uuid, 'includes', 'PE indicators')
|
||||||
pe_sections = []
|
pe_sections = []
|
||||||
|
|
@ -35,44 +41,57 @@ def make_pe_objects(lief_parsed: lief.PE.Binary, misp_file: FileObject, standalo
|
||||||
|
|
||||||
class PEObject(AbstractMISPObjectGenerator):
|
class PEObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, parsed: Optional[lief.PE.Binary] = None, filepath: Optional[Union[Path, str]] = None, pseudofile: Optional[BytesIO] = None, **kwargs):
|
__pe: lief.PE.Binary
|
||||||
|
|
||||||
|
def __init__(self, parsed: lief.PE.Binary | None = None, # type: ignore[no-untyped-def]
|
||||||
|
filepath: Path | str | None = None,
|
||||||
|
pseudofile: BytesIO | list[int] | None = None,
|
||||||
|
**kwargs) -> None:
|
||||||
"""Creates an PE object, with lief"""
|
"""Creates an PE object, with lief"""
|
||||||
super().__init__('pe', **kwargs)
|
super().__init__('pe', **kwargs)
|
||||||
if not HAS_PYDEEP:
|
if not HAS_PYDEEP:
|
||||||
logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]")
|
logger.warning("pydeep is missing, please install pymisp this way: pip install pymisp[fileobjects]")
|
||||||
if pseudofile:
|
if pseudofile:
|
||||||
if isinstance(pseudofile, BytesIO):
|
if isinstance(pseudofile, BytesIO):
|
||||||
self.__pe = lief.PE.parse(io=pseudofile)
|
p = lief.PE.parse(obj=pseudofile)
|
||||||
elif isinstance(pseudofile, bytes):
|
elif isinstance(pseudofile, bytes):
|
||||||
self.__pe = lief.PE.parse(raw=pseudofile)
|
p = lief.PE.parse(raw=list(pseudofile))
|
||||||
|
elif isinstance(pseudofile, list):
|
||||||
|
p = lief.PE.parse(raw=pseudofile)
|
||||||
else:
|
else:
|
||||||
raise InvalidMISPObject('Pseudo file can be BytesIO or bytes got {}'.format(type(pseudofile)))
|
raise InvalidMISPObject(f'Pseudo file can be BytesIO or bytes got {type(pseudofile)}')
|
||||||
|
if not p:
|
||||||
|
raise InvalidMISPObject('Unable to parse pseudofile')
|
||||||
|
self.__pe = p
|
||||||
elif filepath:
|
elif filepath:
|
||||||
self.__pe = lief.PE.parse(filepath)
|
if p := lief.PE.parse(filepath):
|
||||||
|
self.__pe = p
|
||||||
|
else:
|
||||||
|
raise InvalidMISPObject(f'Unable to parse {filepath}')
|
||||||
elif parsed:
|
elif parsed:
|
||||||
# Got an already parsed blob
|
# Got an already parsed blob
|
||||||
if isinstance(parsed, lief.PE.Binary):
|
if isinstance(parsed, lief.PE.Binary):
|
||||||
self.__pe = parsed
|
self.__pe = parsed
|
||||||
else:
|
else:
|
||||||
raise InvalidMISPObject('Not a lief.PE.Binary: {}'.format(type(parsed)))
|
raise InvalidMISPObject(f'Not a lief.PE.Binary: {type(parsed)}')
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def _is_exe(self):
|
def _is_exe(self) -> bool:
|
||||||
if not self._is_dll() and not self._is_driver():
|
if not self._is_dll() and not self._is_driver():
|
||||||
return self.__pe.header.has_characteristic(lief.PE.HEADER_CHARACTERISTICS.EXECUTABLE_IMAGE)
|
return self.__pe.header.has_characteristic(lief.PE.Header.CHARACTERISTICS.EXECUTABLE_IMAGE)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_dll(self):
|
def _is_dll(self) -> bool:
|
||||||
return self.__pe.header.has_characteristic(lief.PE.HEADER_CHARACTERISTICS.DLL)
|
return self.__pe.header.has_characteristic(lief.PE.Header.CHARACTERISTICS.DLL)
|
||||||
|
|
||||||
def _is_driver(self):
|
def _is_driver(self) -> bool:
|
||||||
# List from pefile
|
# List from pefile
|
||||||
system_DLLs = set(('ntoskrnl.exe', 'hal.dll', 'ndis.sys', 'bootvid.dll', 'kdcom.dll'))
|
system_DLLs = {'ntoskrnl.exe', 'hal.dll', 'ndis.sys', 'bootvid.dll', 'kdcom.dll'}
|
||||||
if system_DLLs.intersection([imp.lower() for imp in self.__pe.libraries]):
|
if system_DLLs.intersection([imp.lower() for imp in self.__pe.libraries]):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _get_pe_type(self):
|
def _get_pe_type(self) -> str:
|
||||||
if self._is_dll():
|
if self._is_dll():
|
||||||
return 'dll'
|
return 'dll'
|
||||||
elif self._is_driver():
|
elif self._is_driver():
|
||||||
|
|
@ -82,31 +101,27 @@ class PEObject(AbstractMISPObjectGenerator):
|
||||||
else:
|
else:
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('type', value=self._get_pe_type())
|
self.add_attribute('type', value=self._get_pe_type())
|
||||||
# General information
|
# General information
|
||||||
self.add_attribute('entrypoint-address', value=self.__pe.entrypoint)
|
self.add_attribute('entrypoint-address', value=self.__pe.entrypoint)
|
||||||
self.add_attribute('compilation-timestamp', value=datetime.utcfromtimestamp(self.__pe.header.time_date_stamps).isoformat())
|
self.add_attribute('compilation-timestamp', value=datetime.utcfromtimestamp(self.__pe.header.time_date_stamps).isoformat())
|
||||||
self.add_attribute('imphash', value=lief.PE.get_imphash(self.__pe, lief.PE.IMPHASH_MODE.PEFILE))
|
self.add_attribute('imphash', value=lief.PE.get_imphash(self.__pe, lief.PE.IMPHASH_MODE.PEFILE))
|
||||||
self.add_attribute('authentihash', value=self.__pe.authentihash_sha256.hex())
|
self.add_attribute('authentihash', value=self.__pe.authentihash_sha256.hex())
|
||||||
try:
|
r_manager = self.__pe.resources_manager
|
||||||
if (self.__pe.has_resources
|
if isinstance(r_manager, lief.PE.ResourcesManager):
|
||||||
and self.__pe.resources_manager.has_version
|
version = r_manager.version
|
||||||
and self.__pe.resources_manager.version.has_string_file_info
|
if isinstance(version, lief.PE.ResourceVersion) and version.string_file_info is not None:
|
||||||
and self.__pe.resources_manager.version.string_file_info.langcode_items):
|
fileinfo = dict(version.string_file_info.langcode_items[0].items.items())
|
||||||
fileinfo = dict(self.__pe.resources_manager.version.string_file_info.langcode_items[0].items.items())
|
|
||||||
self.add_attribute('original-filename', value=fileinfo.get('OriginalFilename'))
|
self.add_attribute('original-filename', value=fileinfo.get('OriginalFilename'))
|
||||||
self.add_attribute('internal-filename', value=fileinfo.get('InternalName'))
|
self.add_attribute('internal-filename', value=fileinfo.get('InternalName'))
|
||||||
self.add_attribute('file-description', value=fileinfo.get('FileDescription'))
|
self.add_attribute('file-description', value=fileinfo.get('FileDescription'))
|
||||||
self.add_attribute('file-version', value=fileinfo.get('FileVersion'))
|
self.add_attribute('file-version', value=fileinfo.get('FileVersion'))
|
||||||
self.add_attribute('lang-id', value=self.__pe.resources_manager.version.string_file_info.langcode_items[0].key)
|
|
||||||
self.add_attribute('product-name', value=fileinfo.get('ProductName'))
|
self.add_attribute('product-name', value=fileinfo.get('ProductName'))
|
||||||
self.add_attribute('product-version', value=fileinfo.get('ProductVersion'))
|
self.add_attribute('product-version', value=fileinfo.get('ProductVersion'))
|
||||||
self.add_attribute('company-name', value=fileinfo.get('CompanyName'))
|
self.add_attribute('company-name', value=fileinfo.get('CompanyName'))
|
||||||
self.add_attribute('legal-copyright', value=fileinfo.get('LegalCopyright'))
|
self.add_attribute('legal-copyright', value=fileinfo.get('LegalCopyright'))
|
||||||
except lief.read_out_of_bound:
|
self.add_attribute('lang-id', value=version.string_file_info.langcode_items[0].key)
|
||||||
# The file is corrupted
|
|
||||||
pass
|
|
||||||
# Sections
|
# Sections
|
||||||
self.sections = []
|
self.sections = []
|
||||||
if self.__pe.sections:
|
if self.__pe.sections:
|
||||||
|
|
@ -116,10 +131,14 @@ class PEObject(AbstractMISPObjectGenerator):
|
||||||
# Skip section if name is none AND size is 0.
|
# Skip section if name is none AND size is 0.
|
||||||
continue
|
continue
|
||||||
s = PESectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters)
|
s = PESectionObject(section, standalone=self._standalone, default_attributes_parameters=self._default_attributes_parameters)
|
||||||
self.add_reference(s.uuid, 'includes', 'Section {} of PE'.format(pos))
|
self.add_reference(s.uuid, 'includes', f'Section {pos} of PE')
|
||||||
if ((self.__pe.entrypoint >= section.virtual_address)
|
if ((self.__pe.entrypoint >= section.virtual_address)
|
||||||
and (self.__pe.entrypoint < (section.virtual_address + section.virtual_size))):
|
and (self.__pe.entrypoint < (section.virtual_address + section.virtual_size))):
|
||||||
self.add_attribute('entrypoint-section-at-position', value='{}|{}'.format(section.name, pos))
|
if isinstance(section.name, bytes):
|
||||||
|
section_name = section.name.decode()
|
||||||
|
else:
|
||||||
|
section_name = section.name
|
||||||
|
self.add_attribute('entrypoint-section-at-position', value=f'{section_name}|{pos}')
|
||||||
pos += 1
|
pos += 1
|
||||||
self.sections.append(s)
|
self.sections.append(s)
|
||||||
self.add_attribute('number-sections', value=len(self.sections))
|
self.add_attribute('number-sections', value=len(self.sections))
|
||||||
|
|
@ -139,16 +158,30 @@ class PEObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
class PECertificate(AbstractMISPObjectGenerator):
|
class PECertificate(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, certificate: lief.PE.x509, **kwargs):
|
def __init__(self, certificate: lief.PE.x509, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('x509')
|
super().__init__('x509')
|
||||||
self.__certificate = certificate
|
self.__certificate = certificate
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('issuer', value=self.__certificate.issuer)
|
self.add_attribute('issuer', value=self.__certificate.issuer)
|
||||||
self.add_attribute('serial-number', value=self.__certificate.serial_number)
|
self.add_attribute('serial-number', value=self.__certificate.serial_number)
|
||||||
self.add_attribute('validity-not-before', value=datetime(*self.__certificate.valid_from))
|
if len(self.__certificate.valid_from) == 6:
|
||||||
self.add_attribute('validity-not-after', value=datetime(*self.__certificate.valid_to))
|
self.add_attribute('validity-not-before',
|
||||||
|
value=datetime(year=self.__certificate.valid_from[0],
|
||||||
|
month=self.__certificate.valid_from[1],
|
||||||
|
day=self.__certificate.valid_from[2],
|
||||||
|
hour=self.__certificate.valid_from[3],
|
||||||
|
minute=self.__certificate.valid_from[4],
|
||||||
|
second=self.__certificate.valid_from[5]))
|
||||||
|
if len(self.__certificate.valid_to) == 6:
|
||||||
|
self.add_attribute('validity-not-after',
|
||||||
|
value=datetime(year=self.__certificate.valid_to[0],
|
||||||
|
month=self.__certificate.valid_to[1],
|
||||||
|
day=self.__certificate.valid_to[2],
|
||||||
|
hour=self.__certificate.valid_to[3],
|
||||||
|
minute=self.__certificate.valid_to[4],
|
||||||
|
second=self.__certificate.valid_to[5]))
|
||||||
self.add_attribute('version', value=self.__certificate.version)
|
self.add_attribute('version', value=self.__certificate.version)
|
||||||
self.add_attribute('subject', value=self.__certificate.subject)
|
self.add_attribute('subject', value=self.__certificate.subject)
|
||||||
self.add_attribute('signature_algorithm', value=self.__certificate.signature_algorithm)
|
self.add_attribute('signature_algorithm', value=self.__certificate.signature_algorithm)
|
||||||
|
|
@ -157,19 +190,19 @@ class PECertificate(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
class PESigners(AbstractMISPObjectGenerator):
|
class PESigners(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, signer: lief.PE.SignerInfo, **kwargs):
|
def __init__(self, signer: lief.PE.SignerInfo, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('authenticode-signerinfo')
|
super().__init__('authenticode-signerinfo')
|
||||||
self.__signer = signer
|
self.__signer = signer
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('issuer', value=self.__signer.issuer)
|
self.add_attribute('issuer', value=self.__signer.issuer)
|
||||||
self.add_attribute('serial-number', value=self.__signer.serial_number)
|
self.add_attribute('serial-number', value=self.__signer.serial_number)
|
||||||
self.add_attribute('version', value=self.__signer.version)
|
self.add_attribute('version', value=self.__signer.version)
|
||||||
self.add_attribute('digest_algorithm', value=self.__signer.digest_algorithm.name)
|
self.add_attribute('digest_algorithm', value=str(self.__signer.digest_algorithm))
|
||||||
self.add_attribute('encryption_algorithm', value=self.__signer.encryption_algorithm.name)
|
self.add_attribute('encryption_algorithm', value=str(self.__signer.encryption_algorithm))
|
||||||
self.add_attribute('digest-base64', value=b64encode(self.__signer.encrypted_digest))
|
self.add_attribute('digest-base64', value=b64encode(self.__signer.encrypted_digest))
|
||||||
info = self.__signer.get_attribute(lief.PE.SIG_ATTRIBUTE_TYPES.SPC_SP_OPUS_INFO)
|
info: lief.PE.SpcSpOpusInfo = self.__signer.get_attribute(lief.PE.Attribute.TYPE.SPC_SP_OPUS_INFO) # type: ignore[assignment]
|
||||||
if info:
|
if info:
|
||||||
self.add_attribute('program-name', value=info.program_name)
|
self.add_attribute('program-name', value=info.program_name)
|
||||||
self.add_attribute('url', value=info.more_info)
|
self.add_attribute('url', value=info.more_info)
|
||||||
|
|
@ -177,17 +210,17 @@ class PESigners(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
class PESectionObject(AbstractMISPObjectGenerator):
|
class PESectionObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, section: lief.PE.Section, **kwargs):
|
def __init__(self, section: lief.PE.Section, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
"""Creates an PE Section object. Object generated by PEObject."""
|
"""Creates an PE Section object. Object generated by PEObject."""
|
||||||
super().__init__('pe-section')
|
super().__init__('pe-section')
|
||||||
self.__section = section
|
self.__section = section
|
||||||
self.__data = bytes(self.__section.content)
|
self.__data = bytes(self.__section.content)
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('name', value=self.__section.name)
|
self.add_attribute('name', value=self.__section.name)
|
||||||
size = self.add_attribute('size-in-bytes', value=self.__section.size)
|
self.add_attribute('size-in-bytes', value=self.__section.size)
|
||||||
if int(size.value) > 0:
|
if int(self.__section.size) > 0:
|
||||||
# zero-filled sections can create too many correlations
|
# zero-filled sections can create too many correlations
|
||||||
to_ids = float(self.__section.entropy) > 0
|
to_ids = float(self.__section.entropy) > 0
|
||||||
disable_correlation = not to_ids
|
disable_correlation = not to_ids
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
# Standard imports
|
# Standard imports
|
||||||
import base64
|
import base64
|
||||||
|
|
@ -49,7 +50,7 @@ def create_flowable_tag(misp_tag):
|
||||||
return [Flowable_Tag(text=misp_tag.name, color=misp_tag.colour, custom_style=col1_style)]
|
return [Flowable_Tag(text=misp_tag.name, color=misp_tag.colour, custom_style=col1_style)]
|
||||||
|
|
||||||
|
|
||||||
class Flowable_Tag(Flowable):
|
class Flowable_Tag(Flowable): # type: ignore[misc]
|
||||||
"""
|
"""
|
||||||
Custom flowable to handle tags. Draw one Tag with the webview formatting
|
Custom flowable to handle tags. Draw one Tag with the webview formatting
|
||||||
Modified from : http://two.pairlist.net/pipermail/reportlab-users/2005-February/003695.html
|
Modified from : http://two.pairlist.net/pipermail/reportlab-users/2005-February/003695.html
|
||||||
|
|
@ -108,7 +109,7 @@ class Flowable_Tag(Flowable):
|
||||||
LEFT_INTERNAL_PADDING = 2
|
LEFT_INTERNAL_PADDING = 2
|
||||||
ELONGATION = LEFT_INTERNAL_PADDING * 2
|
ELONGATION = LEFT_INTERNAL_PADDING * 2
|
||||||
|
|
||||||
p = Paragraph("<font color='{}'>{}</font>".format(self.choose_good_text_color(), self.text), style=self.custom_style)
|
p = Paragraph(f"<font color='{self.choose_good_text_color()}'>{self.text}</font>", style=self.custom_style)
|
||||||
string_width = stringWidth(self.text, self.custom_style.fontName, self.custom_style.fontSize)
|
string_width = stringWidth(self.text, self.custom_style.fontName, self.custom_style.fontSize)
|
||||||
|
|
||||||
self.width = string_width + ELONGATION
|
self.width = string_width + ELONGATION
|
||||||
|
|
@ -615,7 +616,7 @@ class Value_Formatter():
|
||||||
curr_uuid = str(is_safe_value(uuid))
|
curr_uuid = str(is_safe_value(uuid))
|
||||||
curr_baseurl = self.config[moduleconfig[0]]
|
curr_baseurl = self.config[moduleconfig[0]]
|
||||||
curr_url = uuid_to_url(curr_baseurl, curr_uuid)
|
curr_url = uuid_to_url(curr_baseurl, curr_uuid)
|
||||||
html_url = "<a href={}>{}</a>".format(curr_url, safe_string(text))
|
html_url = f"<a href={curr_url}>{safe_string(text)}</a>"
|
||||||
|
|
||||||
if color:
|
if color:
|
||||||
# They want fancy colors
|
# They want fancy colors
|
||||||
|
|
@ -744,7 +745,7 @@ class Value_Formatter():
|
||||||
answer = YES_ANSWER
|
answer = YES_ANSWER
|
||||||
if is_safe_value(published_timestamp):
|
if is_safe_value(published_timestamp):
|
||||||
# Published and have published date
|
# Published and have published date
|
||||||
answer += '({})'.format(published_timestamp.strftime(EXPORT_DATE_FORMAT))
|
answer += f'({published_timestamp.strftime(EXPORT_DATE_FORMAT)})'
|
||||||
else:
|
else:
|
||||||
# Published without published date
|
# Published without published date
|
||||||
answer += "(no date)"
|
answer += "(no date)"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
|
|
||||||
|
|
@ -8,13 +9,13 @@ class SBSignatureObject(AbstractMISPObjectGenerator):
|
||||||
'''
|
'''
|
||||||
Sandbox Analyzer
|
Sandbox Analyzer
|
||||||
'''
|
'''
|
||||||
def __init__(self, software: str, report: list, **kwargs):
|
def __init__(self, software: str, report: list[tuple[str, str]], **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('sb-signature', **kwargs)
|
super().__init__('sb-signature', **kwargs)
|
||||||
self._software = software
|
self._software = software
|
||||||
self._report = report
|
self._report = report
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
''' Parse the report for relevant attributes '''
|
''' Parse the report for relevant attributes '''
|
||||||
self.add_attribute("software", value=self._software)
|
self.add_attribute("software", value=self._software)
|
||||||
for (signature_name, description) in self._report:
|
for (signature_name, description) in self._report:
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,25 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from io import StringIO
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from ..exceptions import InvalidMISPObject
|
from ..exceptions import InvalidMISPObject
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
from io import StringIO
|
|
||||||
import logging
|
|
||||||
from typing import Optional, Union
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
logger = logging.getLogger('pymisp')
|
logger = logging.getLogger('pymisp')
|
||||||
|
|
||||||
|
|
||||||
class SSHAuthorizedKeysObject(AbstractMISPObjectGenerator):
|
class SSHAuthorizedKeysObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, authorized_keys_path: Optional[Union[Path, str]] = None, authorized_keys_pseudofile: Optional[StringIO] = None, **kwargs):
|
def __init__(self, authorized_keys_path: Path | str | None = None, # type: ignore[no-untyped-def]
|
||||||
# PY3 way:
|
authorized_keys_pseudofile: StringIO | None = None, **kwargs):
|
||||||
super().__init__('ssh-authorized-keys', **kwargs)
|
super().__init__('ssh-authorized-keys', **kwargs)
|
||||||
if authorized_keys_path:
|
if authorized_keys_path:
|
||||||
with open(authorized_keys_path, 'r') as f:
|
with open(authorized_keys_path) as f:
|
||||||
self.__pseudofile = StringIO(f.read())
|
self.__pseudofile = StringIO(f.read())
|
||||||
elif authorized_keys_pseudofile and isinstance(authorized_keys_pseudofile, StringIO):
|
elif authorized_keys_pseudofile and isinstance(authorized_keys_pseudofile, StringIO):
|
||||||
self.__pseudofile = authorized_keys_pseudofile
|
self.__pseudofile = authorized_keys_pseudofile
|
||||||
|
|
@ -26,7 +28,7 @@ class SSHAuthorizedKeysObject(AbstractMISPObjectGenerator):
|
||||||
self.__data = self.__pseudofile.getvalue()
|
self.__data = self.__pseudofile.getvalue()
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
for line in self.__pseudofile:
|
for line in self.__pseudofile:
|
||||||
if line.startswith('ssh') or line.startswith('ecdsa'):
|
if line.startswith('ssh') or line.startswith('ecdsa'):
|
||||||
key = line.split(' ')[1]
|
key = line.split(' ')[1]
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
try:
|
|
||||||
from misp_stix_converter.converters.buildMISPAttribute import buildEvent # type: ignore
|
|
||||||
from misp_stix_converter.converters import convert # type: ignore
|
|
||||||
from misp_stix_converter.converters.convert import MISPtoSTIX # type: ignore
|
|
||||||
has_misp_stix_converter = True
|
|
||||||
except ImportError:
|
|
||||||
has_misp_stix_converter = False
|
|
||||||
|
|
||||||
|
|
||||||
def load_stix(stix, distribution: int = 3, threat_level_id: int = 2, analysis: int = 0):
|
|
||||||
'''Returns a MISPEvent object from a STIX package'''
|
|
||||||
if not has_misp_stix_converter:
|
|
||||||
raise Exception('You need to install misp_stix_converter: pip install git+https://github.com/MISP/MISP-STIX-Converter.git')
|
|
||||||
stix = convert.load_stix(stix)
|
|
||||||
return buildEvent(stix, distribution=distribution,
|
|
||||||
threat_level_id=threat_level_id, analysis=analysis)
|
|
||||||
|
|
||||||
|
|
||||||
def make_stix_package(misp_event, to_json: bool = False, to_xml: bool = False):
|
|
||||||
'''Returns a STIXPackage from a MISPEvent.
|
|
||||||
|
|
||||||
Optionally can return the package in json or xml.
|
|
||||||
|
|
||||||
'''
|
|
||||||
if not has_misp_stix_converter:
|
|
||||||
raise Exception('You need to install misp_stix_converter: pip install git+https://github.com/MISP/MISP-STIX-Converter.git')
|
|
||||||
package = MISPtoSTIX(misp_event)
|
|
||||||
if to_json:
|
|
||||||
return package.to_json()
|
|
||||||
elif to_xml:
|
|
||||||
return package.to_xml()
|
|
||||||
else:
|
|
||||||
return package
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import zipfile
|
import zipfile
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
@ -12,7 +13,7 @@ from ..abstract import resources_path
|
||||||
static_repo = "https://github.com/MISP/misp-objects/archive/main.zip"
|
static_repo = "https://github.com/MISP/misp-objects/archive/main.zip"
|
||||||
|
|
||||||
|
|
||||||
def update_objects():
|
def update_objects() -> None:
|
||||||
r = requests.get(static_repo)
|
r = requests.get(static_repo)
|
||||||
|
|
||||||
zipped_repo = BytesIO(r.content)
|
zipped_repo = BytesIO(r.content)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from urllib.parse import unquote_plus
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
import logging
|
|
||||||
from urllib.parse import unquote_plus
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pyfaup.faup import Faup # type: ignore
|
from pyfaup.faup import Faup # type: ignore
|
||||||
|
|
@ -17,13 +20,13 @@ faup = Faup()
|
||||||
|
|
||||||
class URLObject(AbstractMISPObjectGenerator):
|
class URLObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
def __init__(self, url: str, generate_all=False, **kwargs):
|
def __init__(self, url: str, generate_all=False, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('url', **kwargs)
|
super().__init__('url', **kwargs)
|
||||||
self._generate_all = True if generate_all is True else False
|
self._generate_all = True if generate_all is True else False
|
||||||
faup.decode(unquote_plus(url))
|
faup.decode(unquote_plus(url))
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
self.add_attribute('url', value=faup.url.decode())
|
self.add_attribute('url', value=faup.url.decode())
|
||||||
if faup.get_host():
|
if faup.get_host():
|
||||||
self.add_attribute('host', value=faup.get_host())
|
self.add_attribute('host', value=faup.get_host())
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from .abstractgenerator import AbstractMISPObjectGenerator
|
from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
|
|
||||||
# Original sourcecode: https://github.com/hayk57/MISP_registration_check
|
# Original sourcecode: https://github.com/hayk57/MISP_registration_check
|
||||||
|
|
@ -11,14 +15,16 @@ from .abstractgenerator import AbstractMISPObjectGenerator
|
||||||
class VehicleObject(AbstractMISPObjectGenerator):
|
class VehicleObject(AbstractMISPObjectGenerator):
|
||||||
'''Vehicle object generator out of regcheck.org.uk'''
|
'''Vehicle object generator out of regcheck.org.uk'''
|
||||||
|
|
||||||
country_urls = {
|
country_urls: dict[str, str] = {
|
||||||
'fr': "http://www.regcheck.org.uk/api/reg.asmx/CheckFrance",
|
'fr': "http://www.regcheck.org.uk/api/reg.asmx/CheckFrance",
|
||||||
'es': "http://www.regcheck.org.uk/api/reg.asmx/CheckSpain",
|
'es': "http://www.regcheck.org.uk/api/reg.asmx/CheckSpain",
|
||||||
'uk': "http://www.regcheck.org.uk/api/reg.asmx/Check"
|
'uk': "http://www.regcheck.org.uk/api/reg.asmx/Check"
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, country: str, registration: str, username: str, **kwargs):
|
def __init__(self, country: str, registration: str, username: str, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('vehicle', **kwargs)
|
super().__init__('vehicle', **kwargs)
|
||||||
|
if country not in self.country_urls:
|
||||||
|
raise ValueError(f"Country {country} not supportet, must be one of {self.country_urls.keys()}")
|
||||||
self._country = country
|
self._country = country
|
||||||
self._registration = registration
|
self._registration = registration
|
||||||
self._username = username
|
self._username = username
|
||||||
|
|
@ -26,10 +32,10 @@ class VehicleObject(AbstractMISPObjectGenerator):
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def report(self):
|
def report(self) -> dict[str, Any]:
|
||||||
return self._report
|
return self._report
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
carDescription = self._report["Description"]
|
carDescription = self._report["Description"]
|
||||||
carMake = self._report["CarMake"]["CurrentTextValue"]
|
carMake = self._report["CarMake"]["CurrentTextValue"]
|
||||||
carModel = self._report["CarModel"]["CurrentTextValue"]
|
carModel = self._report["CarModel"]["CurrentTextValue"]
|
||||||
|
|
@ -65,14 +71,14 @@ class VehicleObject(AbstractMISPObjectGenerator):
|
||||||
self.add_attribute('date-first-registration', type='text', value=firstRegistration)
|
self.add_attribute('date-first-registration', type='text', value=firstRegistration)
|
||||||
self.add_attribute('image-url', type='text', value=ImageUrl)
|
self.add_attribute('image-url', type='text', value=ImageUrl)
|
||||||
|
|
||||||
def _query(self):
|
def _query(self) -> dict[str, Any]:
|
||||||
payload = "RegistrationNumber={}&username={}".format(self._registration, self._username)
|
payload = f"RegistrationNumber={self._registration}&username={self._username}"
|
||||||
headers = {
|
headers = {
|
||||||
'Content-Type': "application/x-www-form-urlencoded",
|
'Content-Type': "application/x-www-form-urlencoded",
|
||||||
'cache-control': "no-cache",
|
'cache-control': "no-cache",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.request("POST", self.country_urls.get(self._country), data=payload, headers=headers)
|
response = requests.request("POST", self.country_urls[self._country], data=payload, headers=headers)
|
||||||
# FIXME: Clean that up.
|
# FIXME: Clean that up.
|
||||||
for item in response.text.split("</vehicleJson>"):
|
for item in response.text.split("</vehicleJson>"):
|
||||||
if "<vehicleJson>" in item:
|
if "<vehicleJson>" in item:
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Optional
|
from typing import Any
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
try:
|
try:
|
||||||
|
|
@ -24,7 +25,7 @@ class VTReportObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
:indicator: IOC to search VirusTotal for
|
:indicator: IOC to search VirusTotal for
|
||||||
'''
|
'''
|
||||||
def __init__(self, apikey: str, indicator: str, vt_proxies: Optional[dict] = None, **kwargs):
|
def __init__(self, apikey: str, indicator: str, vt_proxies: dict[str, str] | None = None, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||||
super().__init__('virustotal-report', **kwargs)
|
super().__init__('virustotal-report', **kwargs)
|
||||||
indicator = indicator.strip()
|
indicator = indicator.strip()
|
||||||
self._resource_type = self.__validate_resource(indicator)
|
self._resource_type = self.__validate_resource(indicator)
|
||||||
|
|
@ -33,20 +34,20 @@ class VTReportObject(AbstractMISPObjectGenerator):
|
||||||
self._report = self.__query_virustotal(apikey, indicator)
|
self._report = self.__query_virustotal(apikey, indicator)
|
||||||
self.generate_attributes()
|
self.generate_attributes()
|
||||||
else:
|
else:
|
||||||
error_msg = "A valid indicator is required. (One of type url, md5, sha1, sha256). Received '{}' instead".format(indicator)
|
error_msg = f"A valid indicator is required. (One of type url, md5, sha1, sha256). Received '{indicator}' instead"
|
||||||
raise InvalidMISPObject(error_msg)
|
raise InvalidMISPObject(error_msg)
|
||||||
|
|
||||||
def get_report(self):
|
def get_report(self) -> dict[str, Any]:
|
||||||
return self._report
|
return self._report
|
||||||
|
|
||||||
def generate_attributes(self):
|
def generate_attributes(self) -> None:
|
||||||
''' Parse the VirusTotal report for relevant attributes '''
|
''' Parse the VirusTotal report for relevant attributes '''
|
||||||
self.add_attribute("last-submission", value=self._report["scan_date"])
|
self.add_attribute("last-submission", value=self._report["scan_date"])
|
||||||
self.add_attribute("permalink", value=self._report["permalink"])
|
self.add_attribute("permalink", value=self._report["permalink"])
|
||||||
ratio = "{}/{}".format(self._report["positives"], self._report["total"])
|
ratio = "{}/{}".format(self._report["positives"], self._report["total"])
|
||||||
self.add_attribute("detection-ratio", value=ratio)
|
self.add_attribute("detection-ratio", value=ratio)
|
||||||
|
|
||||||
def __validate_resource(self, ioc: str):
|
def __validate_resource(self, ioc: str) -> str | bool:
|
||||||
'''
|
'''
|
||||||
Validate the data type of an indicator.
|
Validate the data type of an indicator.
|
||||||
Domains and IP addresses aren't supported because
|
Domains and IP addresses aren't supported because
|
||||||
|
|
@ -62,7 +63,7 @@ class VTReportObject(AbstractMISPObjectGenerator):
|
||||||
return "file"
|
return "file"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __query_virustotal(self, apikey: str, resource: str):
|
def __query_virustotal(self, apikey: str, resource: str) -> dict[str, Any]:
|
||||||
'''
|
'''
|
||||||
Query VirusTotal for information about an indicator
|
Query VirusTotal for information about an indicator
|
||||||
|
|
||||||
|
|
@ -70,7 +71,7 @@ class VTReportObject(AbstractMISPObjectGenerator):
|
||||||
|
|
||||||
:resource: Indicator to search in VirusTotal
|
:resource: Indicator to search in VirusTotal
|
||||||
'''
|
'''
|
||||||
url = "https://www.virustotal.com/vtapi/v2/{}/report".format(self._resource_type)
|
url = f"https://www.virustotal.com/vtapi/v2/{self._resource_type}/report"
|
||||||
params = {"apikey": apikey, "resource": resource}
|
params = {"apikey": apikey, "resource": resource}
|
||||||
# for now assume we're using a public API key - we'll figure out private keys later
|
# for now assume we're using a public API key - we'll figure out private keys later
|
||||||
if self._proxies:
|
if self._proxies:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pymisp"
|
name = "pymisp"
|
||||||
version = "2.4.178"
|
version = "2.4.185"
|
||||||
description = "Python API for MISP."
|
description = "Python API for MISP."
|
||||||
authors = ["Raphaël Vinot <raphael.vinot@circl.lu>"]
|
authors = ["Raphaël Vinot <raphael.vinot@circl.lu>"]
|
||||||
license = "BSD-2-Clause"
|
license = "BSD-2-Clause"
|
||||||
|
|
@ -21,6 +21,7 @@ classifiers=[
|
||||||
'Programming Language :: Python :: 3.9',
|
'Programming Language :: Python :: 3.9',
|
||||||
'Programming Language :: Python :: 3.10',
|
'Programming Language :: Python :: 3.10',
|
||||||
'Programming Language :: Python :: 3.11',
|
'Programming Language :: Python :: 3.11',
|
||||||
|
'Programming Language :: Python :: 3.12',
|
||||||
'Topic :: Security',
|
'Topic :: Security',
|
||||||
'Topic :: Internet'
|
'Topic :: Internet'
|
||||||
]
|
]
|
||||||
|
|
@ -43,21 +44,20 @@ include = [
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
requests = "^2.31.0"
|
requests = "^2.31.0"
|
||||||
python-dateutil = "^2.8.2"
|
python-dateutil = "^2.8.2"
|
||||||
jsonschema = ">=4.17.3"
|
|
||||||
deprecated = "^1.2.14"
|
deprecated = "^1.2.14"
|
||||||
extract_msg = {version = "^0.45.0", optional = true}
|
extract_msg = {version = "^0.47.0", optional = true}
|
||||||
RTFDE = {version = "^0.1.0", optional = true}
|
RTFDE = {version = "^0.1.1", optional = true}
|
||||||
oletools = {version = "^0.60.1", optional = true}
|
oletools = {version = "^0.60.1", optional = true}
|
||||||
python-magic = {version = "^0.4.27", optional = true}
|
python-magic = {version = "^0.4.27", optional = true}
|
||||||
pydeep2 = {version = "^0.5.1", optional = true}
|
pydeep2 = {version = "^0.5.1", optional = true}
|
||||||
lief = {version = "^0.13.2", optional = true}
|
lief = {version = "^0.14.1", optional = true}
|
||||||
beautifulsoup4 = {version = "^4.12.2", optional = true}
|
beautifulsoup4 = {version = "^4.12.3", optional = true}
|
||||||
validators = {version = "^0.22.0", optional = true}
|
validators = {version = "^0.22.0", optional = true}
|
||||||
sphinx-autodoc-typehints = {version = "^1.24.0", optional = true}
|
sphinx-autodoc-typehints = {version = "^2.0.0", optional = true}
|
||||||
recommonmark = {version = "^0.7.1", optional = true}
|
recommonmark = {version = "^0.7.1", optional = true}
|
||||||
reportlab = {version = "^4.0.6", optional = true}
|
reportlab = {version = "^4.1.0", optional = true}
|
||||||
pyfaup = {version = "^1.2", optional = true}
|
pyfaup = {version = "^1.2", optional = true}
|
||||||
publicsuffixlist = {version = "^0.10.0.20231022", optional = true}
|
publicsuffixlist = {version = "^0.10.0.20231214", optional = true}
|
||||||
urllib3 = {extras = ["brotli"], version = "*", optional = true}
|
urllib3 = {extras = ["brotli"], version = "*", optional = true}
|
||||||
Sphinx = [
|
Sphinx = [
|
||||||
{version = "<7.2", python = "<3.9", optional = true},
|
{version = "<7.2", python = "<3.9", optional = true},
|
||||||
|
|
@ -76,15 +76,16 @@ brotli = ['urllib3']
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
requests-mock = "^1.11.0"
|
requests-mock = "^1.11.0"
|
||||||
mypy = "^1.6.1"
|
mypy = "^1.8.0"
|
||||||
ipython = [
|
ipython = [
|
||||||
{version = "<8.13.0", python = "<3.9"},
|
{version = "<8.13.0", python = "<3.9"},
|
||||||
{version = "^8.13.0", python = ">=3.9"}
|
{version = "^8.18.0", python = ">=3.9"},
|
||||||
|
{version = "^8.19.0", python = ">=3.10"}
|
||||||
]
|
]
|
||||||
jupyterlab = "^4.0.7"
|
jupyterlab = "^4.1.2"
|
||||||
types-requests = "^2.31.0.10"
|
types-requests = "^2.31.0.20240218"
|
||||||
types-python-dateutil = "^2.8.19.14"
|
types-python-dateutil = "^2.8.19.20240106"
|
||||||
types-redis = "^4.6.0.7"
|
types-redis = "^4.6.0.20240218"
|
||||||
types-Flask = "^1.1.6"
|
types-Flask = "^1.1.6"
|
||||||
pytest-cov = "^4.1.0"
|
pytest-cov = "^4.1.0"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,858 +0,0 @@
|
||||||
Return-Path: <suvorov.s@nalg.ru>
|
|
||||||
Delivered-To: kinney@noth.com
|
|
||||||
Received: (qmail 11769 invoked from network); 22 Aug 2016 14:23:01 -0000
|
|
||||||
Received: from smtprelay0207.b.hostedemail.com (HELO smtprelay.b.hostedemail.com) (64.98.42.207)
|
|
||||||
by smtp.server.net with SMTP; 22 Aug 2016 14:23:01 -0000
|
|
||||||
Received: from filter.hostedemail.com (10.5.19.248.rfc1918.com [10.5.19.248])
|
|
||||||
by smtprelay06.b.hostedemail.com (Postfix) with ESMTP id 2CC378D014
|
|
||||||
for <kinney@noth.com>; Mon, 22 Aug 2016 14:22:58 +0000 (UTC)
|
|
||||||
Received: from DM6PR06MB4475.namprd06.prod.outlook.com (2603:10b6:207:3d::31)
|
|
||||||
by BL0PR06MB4465.namprd06.prod.outlook.com with HTTPS id 12345 via
|
|
||||||
BL0PR02CA0054.NAMPRD02.PROD.OUTLOOK.COM; Mon, 1 Oct 2018 09:49:22 +0000
|
|
||||||
Received: from DM3NAM03FT035.eop-NAM03.prod.protection.outlook.com
|
|
||||||
(2a01:111:f400:7e49::205) by CY4PR0601CA0051.outlook.office365.com
|
|
||||||
(2603:10b6:910:89::28) with Microsoft SMTP Server (version=TLS1_2,
|
|
||||||
cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) id 15.20.1185.23 via Frontend
|
|
||||||
Transport; Mon, 1 Oct 2018 09:49:21 +0000
|
|
||||||
X-Session-Marker: 6A64617A657940616C6578616E646572736D6974682E636F6D
|
|
||||||
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
|
|
||||||
X-HE-Tag: print38_7083d7fd63e24
|
|
||||||
X-Filterd-Recvd-Size: 64695
|
|
||||||
Received: from computer_3436 (unknown [43.230.105.145])
|
|
||||||
(Authenticated sender: jdazey@alexandersmith.com)
|
|
||||||
by omf06.b.hostedemail.com (Postfix) with ESMTPA
|
|
||||||
for <kinney@noth.com>; Mon, 22 Aug 2016 14:22:52 +0000 (UTC)
|
|
||||||
From: =?UTF-8?B?0YHQu9GD0LbQsdCwINCk0J3QoSDQlNCw0L3QuNC40Lsg0KHRg9Cy0L7RgNC+0LI=?= <suvorov.s@nalg.ru>
|
|
||||||
To: kinney@noth.com
|
|
||||||
Subject: =?UTF-8?B?0L/QuNGB0YzQvNC+INGD0LLQtdC00L7QvC3QtQ==?=
|
|
||||||
Content-Type: multipart/mixed; boundary="2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7"
|
|
||||||
|
|
||||||
--2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7
|
|
||||||
Content-Type: text/html; charset=UTF-8
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
|
|
||||||
0J3QsNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC40LohPGJyPg0K0JjQvdGE0L7RgNC80LjRgNGD
|
|
||||||
0LXQvCDQktCw0YEg0L7QsSDQuNC80LXRjtGJ0LXQudGB0Y8g0LfQsNC00L7Qu9C20LXQvdC90L7R
|
|
||||||
gdGC0LguPHA+DQrQn9GA0L7RgdGM0LHQsCDQvtC30L3QsNC60L7QvNC40YLRjNGB0Y8g0LIg0L/R
|
|
||||||
gNC40LvQvtC20LXQvdC40LguPHA+DQo8YnI+DQo8YnI+DQrQoSDRg9Cy0LDQttC10L3QuNC10Lws
|
|
||||||
PHA+DQrQuNC90YHQv9C10LrRgtC+0YAg0KTQndChINCg0KQg0JXQs9C+0YAg0KHRg9Cy0L7RgNC+
|
|
||||||
0LIuPGJyPg0KPHA+DQo8YnI+DQo8cD4NCjxwPg0K0JjQvdGE0L7RgNC80LDRhtC40L7QvdC90YvQ
|
|
||||||
uSDRgNCw0LfQtNC10Ls8YnI+DQo8YnI+DQrQn9GA0LXQt9C40LTQtdC90YIg0L/QvtGA0YPRh9C4
|
|
||||||
0Lsg0L/RgNCw0LLQuNGC0LXQu9GM0YHRgtCy0YMg0LfQsNC60YDQtdC/0LjRgtGMINCyINC30LDQ
|
|
||||||
utC+0L3QtSDRgNC40YHQui3QvtGA0LjQtdC90YLQuNGA0L7QstCw0L3QvdGL0Lkg0L/QvtC00YXQ
|
|
||||||
vtC0INC6INC/0YDQvtCy0LXRgNC60LDQvDxwPg0KPHA+DQoxNSDQsNCy0LPRg9GB0YLQsCAyMDE2
|
|
||||||
PGJyPg0K0JLQsNC70LXRgNC40Y8g0JfQtdC90L7QstC40L3QsDxicj4NCjxwPg0K0J/RgNC10LfQ
|
|
||||||
uNC00LXQvdGCINC/0L7RgNGD0YfQuNC7INC/0YDQsNCy0LjRgtC10LvRjNGB0YLQstGDINC30LDQ
|
|
||||||
utGA0LXQv9C40YLRjCDQsiDQt9Cw0LrQvtC90LUg0YDQuNGB0Lot0L7RgNC40LXQvdGC0LjRgNC+
|
|
||||||
0LLQsNC90L3Ri9C5INC/0L7QtNGF0L7QtCDQuiDQv9GA0L7QstC10YDQutCw0LzQktCy0LXRgdGC
|
|
||||||
0Lgg0YLQsNC60L7QuSDQv9C+0LTRhdC+0LQg0L/RgNC4INC+0YDQs9Cw0L3QuNC30LDRhtC40Lgg
|
|
||||||
0LrQvtC90YLRgNC+0LvRjNC90L4t0L3QsNC00LfQvtGA0L3Ri9GFINC80LXRgNC+0L/RgNC40Y/R
|
|
||||||
gtC40Lkg0Lgg0LIg0YbQtdC70L7QvCDQvtC/0YLQuNC80LjQt9C40YDQvtCy0LDRgtGMINC/0L7Q
|
|
||||||
tNC+0LHQvdGL0LUg0LzQtdGA0L7Qv9GA0LjRj9GC0LjRjyDQvdCwINGA0LXQs9C40L7QvdCw0LvR
|
|
||||||
jNC90L7QvCDQuCDQvNGD0L3QuNGG0LjQv9Cw0LvRjNC90L7QvCDRg9GA0L7QstC90Y/RhSDQn9GA
|
|
||||||
0LXQt9C40LTQtdC90YIg0KDQpCDQktC70LDQtNC40LzQuNGAINCf0YPRgtC40L0g0L/QvtGA0YPR
|
|
||||||
h9C40Lsg0LrQsNCx0LzQuNC90YMg0LTQviAzMSDQtNC10LrQsNCx0YDRjyDRgtC10LrRg9GJ0LXQ
|
|
||||||
s9C+INCz0L7QtNCwLiDQmNC30LzQtdC90LXQvdC40Y8g0LzQvtCz0YPRgiDQutC+0YHQvdGD0YLR
|
|
||||||
jNGB0Y8g0Lgg0LLQvdC10L/Qu9Cw0L3QvtCy0YvRhSDQv9GA0L7QstC10YDQvtC6LiDQkiDRgdC+
|
|
||||||
0L7RgtCy0LXRgtGB0YLQstC40Lgg0YEg0YPQutCw0LfQsNC90LjRj9C80Lgg0L/RgNC10LfQuNC0
|
|
||||||
0LXQvdGC0LAsINCT0LXQvdC10YDQsNC70YzQvdCw0Y8g0L/RgNC+0LrRg9GA0LDRgtGD0YDQsCDQ
|
|
||||||
oNCkINC00L7Qu9C20L3QsCDQstC90LXRgdGC0Lgg0LIg0LfQsNC60L7QvdC+0LTQsNGC0LXQu9GM
|
|
||||||
0YHRgtCy0L4g0L/QvtC/0YDQsNCy0LrQuCwg0L/RgNC10LTRg9GB0LzQsNGC0YDQuNCy0LDRjtGJ
|
|
||||||
0LjQtSDRgdC+0LPQu9Cw0YHQvtCy0LDQvdC40LUg0YLQsNC60LjRhSDQv9GA0L7QstC10YDQvtC6
|
|
||||||
INGBINC+0YDQs9Cw0L3QsNC80Lgg0L/RgNC+0LrRg9GA0LDRgtGD0YDRiy4g0K3RgtC+INC30LDR
|
|
||||||
gtGA0L7QvdC10YIg0L/RgNC+0LLQtdGA0LrQuCwg0LjQvdC40YbQuNC40YDRg9C10LzRi9C1INCy
|
|
||||||
INGB0LLRj9C30Lgg0YEg0L3QsNGA0YPRiNC10L3QuNC10Lwg0L/RgNCw0LIg0L/QvtGC0YDQtdCx
|
|
||||||
0LjRgtC10LvQtdC5LCDQuCDQvdC10LrQvtGC0L7RgNGL0LUg0LTRgNGD0LPQuNC1LjxwPg0KPGJy
|
|
||||||
Pg0K0J3QsNGA0Y/QtNGDINGBINGN0YLQuNC8LCDRgNGP0LQg0LfQsNC60L7QvdC+0LTQsNGC0LXQ
|
|
||||||
u9GM0L3Ri9GFINC40LfQvNC10L3QtdC90LjQuSDQvNC+0LbQtdGCINC60L7RgdC90YPRgtGM0YHR
|
|
||||||
jyDQtdC00LjQvdC+0LPQviDRgNC10LXRgdGC0YDQsCDQv9GA0L7QstC10YDQvtC6IChwcm92ZXJr
|
|
||||||
aS5nb3YucnUpLjxwPg0KPGJyPg0K0KLQsNC6LCDQvtGA0LPQsNC90YssINGA0LXQsNC70LjQt9GD
|
|
||||||
0Y7RidC40LUg0LrQvtC90YLRgNC+0LvRjNC90L4t0L3QsNC00LfQvtGA0L3Ri9C1INC/0L7Qu9C9
|
|
||||||
0L7QvNC+0YfQuNGPLCDQtNC+0LvQttC90Ysg0LHRg9C00YPRgiDQv9C+INGB0L7Qs9C70LDRgdC+
|
|
||||||
0LLQsNC90LjRjiDRgSDQk9C10L3QtdGA0LDQu9GM0L3QvtC5INC/0YDQvtC60YPRgNCw0YLRg9GA
|
|
||||||
0L7QuSDQoNCkINGA0LDQt9GA0LDQsdC+0YLQsNGC0Ywg0Lgg0LjQt9C00LDRgtGMINCw0LrRgtGL
|
|
||||||
LCDRgNC10LPQu9Cw0LzQtdC90YLQuNGA0YPRjtGJ0LjQtSDQv9C+0YDRj9C00L7QuiDQstC90LXR
|
|
||||||
gdC10L3QuNGPINC40L3RhNC+0YDQvNCw0YbQuNC4INC+INC/0YDQvtCy0LXRgNC60LDRhSDQsiDQ
|
|
||||||
tdC00LjQvdGL0Lkg0YDQtdC10YHRgtGAINC/0YDQvtCy0LXRgNC+0LouINCQINC30LAg0L3QtdCy
|
|
||||||
0L3QtdGB0LXQvdC40LUg0YLQsNC60LjRhSDRgdCy0LXQtNC10L3QuNC5INC4INC30LAg0L3QsNGA
|
|
||||||
0YPRiNC10L3QuNC1INC/0L7RgNGP0LTQutCwINC4INGB0YDQvtC60L7QsiDQuNGFINCy0L3QtdGB
|
|
||||||
0LXQvdC40Y8g0L/RgNC10LTQu9Cw0LPQsNC10YLRgdGPINGD0YHRgtCw0L3QvtCy0LjRgtGMINCw
|
|
||||||
0LTQvNC40L3QuNGB0YLRgNCw0YLQuNCy0L3Rg9GOINC+0YLQstC10YLRgdGC0LLQtdC90L3QvtGB
|
|
||||||
0YLRjC4g0JrQsNC60LjQtSDQuNC80LXQvdC90L4g0L3QsNC60LDQt9Cw0L3QuNGPINC80L7Qs9GD
|
|
||||||
0YIg0LHRi9GC0Ywg0LLQstC10LTQtdC90Ysg0LIg0JrQvtCQ0J8g0KDQpCDigJMg0L3QtSDRg9GC
|
|
||||||
0L7Rh9C90Y/QtdGC0YHRjy4g0J7QttC40LTQsNC10YLRgdGPLCDRh9GC0L4g0YPQutCw0LfQsNC9
|
|
||||||
0L3Ri9C1INC/0L7RgNGD0YfQtdC90LjRjyDQsdGD0LTRg9GCINC40YHQv9C+0LvQvdC10L3RiyDQ
|
|
||||||
uiAxINC00LXQutCw0LHRgNGPLjxicj4NCjxwPg0K0JrRgNC+0LzQtSDRgtC+0LPQviwg0LTQviAx
|
|
||||||
NSDQtNC10LrQsNCx0YDRjyDQk9C10L3QtdGA0LDQu9GM0L3QvtC5INC/0YDQvtC60YPRgNCw0YLR
|
|
||||||
g9GA0LUg0KDQpCDQvdC10L7QsdGF0L7QtNC40LzQviDQsdGD0LTQtdGCINC00L7RgNCw0LHQvtGC
|
|
||||||
0LDRgtGMINC10LTQuNC90YvQuSDRgNC10LXRgdGC0YAg0L/RgNC+0LLQtdGA0L7QuiDRgtCw0Los
|
|
||||||
INGH0YLQvtCx0Ysg0L/RgNC4INCy0L3QtdGB0LXQvdC40Lgg0LIg0L3QtdCz0L4g0LjQvdGE0L7R
|
|
||||||
gNC80LDRhtC40Lgg0YHRgtCw0LvQviDQstC+0LfQvNC+0LbQvdGL0Lwg0LjRgdC/0L7Qu9GM0LfQ
|
|
||||||
vtCy0LDQvdC40LUg0YHQstC10LTQtdC90LjQuSDQuNC3INC00YDRg9Cz0LjRhSDQs9C+0YHRg9C0
|
|
||||||
0LDRgNGB0YLQstC10L3QvdGL0YUg0LjQvdGE0L7RgNC80LDRhtC40L7QvdC90YvRhSDRgdC40YHR
|
|
||||||
gtC10LwsINGB0L/RgNCw0LLQvtGH0L3QuNC60L7QsiDQuCDQutC70LDRgdGB0LjRhNC40LrQsNGC
|
|
||||||
0L7RgNC+0LIuINCi0LDQutC20LUg0LPQu9Cw0LLQsCDQs9C+0YHRg9C00LDRgNGB0YLQstCwINGD
|
|
||||||
0LrQsNC30LDQuywg0YfRgtC+INC90LXQvtCx0YXQvtC00LjQvNCwINGE0L7RgNC80LDQu9C40LfQ
|
|
||||||
sNGG0LjRjyDQuCDQutC+0L3QutGA0LXRgtC40LfQsNGG0LjRjyDQstC90L7RgdC40LzQvtC5INCy
|
|
||||||
INGA0LXQtdGB0YLRgCDQuNC90YTQvtGA0LzQsNGG0LjQuCwg0LXRgdC70Lgg0L7QvdCwINC60LDR
|
|
||||||
gdCw0LXRgtGB0Y8g0L/RgNCw0LLQvtCy0YvRhSDQvtGB0L3QvtCy0LDQvdC40Lkg0LTQu9GPINC+
|
|
||||||
0YDQs9Cw0L3QuNC30LDRhtC40Lgg0L/RgNC+0LLQtdGA0L7Qui4g0KDQtdGH0Ywg0LjQtNC10YIg
|
|
||||||
0Lgg0L4g0YTQvtGA0LzQsNC70LjQt9Cw0YbQuNC4INGC0YDQtdCx0L7QstCw0L3QuNC5LCDRgdC+
|
|
||||||
0LHQu9GO0LTQtdC90LjQtSDQutC+0YLQvtGA0YvRhSDQvtGG0LXQvdC40LLQsNC10YLRgdGPINC/
|
|
||||||
0YDQuCDQv9GA0L7QstC10LTQtdC90LjQuCDRgdC+0L7RgtCy0LXRgtGB0YLQstGD0Y7RidC40YUg
|
|
||||||
0LzQtdGA0L7Qv9GA0LjRj9GC0LjQuSwg0LAg0YLQsNC60LbQtSDRgNC10LfRg9C70YzRgtCw0YLQ
|
|
||||||
vtCyINC/0YDQvtCy0LXRgNC+0Log0Lgg0L/RgNC40L3Rj9GC0YvRhSDQvNC10YAuPHA+DQo8YnI+
|
|
||||||
DQrQn9C+0LzQuNC80L4g0Y3RgtC+0LPQviDQv9GA0LXQt9C40LTQtdC90YIg0L/QvtGA0YPRh9C4
|
|
||||||
0Lsg0L/RgNCw0LLQuNGC0LXQu9GM0YHRgtCy0YMg0YDQsNGB0YHQvNC+0YLRgNC10YLRjCDQstC+
|
|
||||||
0L/RgNC+0YEg0L4g0YHRgtC40LzRg9C70LjRgNGD0Y7RidC40YUg0LLRi9C/0LvQsNGC0LDRhSDQ
|
|
||||||
tNC70Y8g0YHQvtGC0YDRg9C00L3QuNC60L7QsiDRhNC10LTQtdGA0LDQu9GM0L3Ri9GFINC40YHQ
|
|
||||||
v9C+0LvQvdC40YLQtdC70YzQvdGL0YUg0L7RgNCz0LDQvdC+0LIsINC60L7RgtC+0YDRi9C1INC+
|
|
||||||
0YHRg9GJ0LXRgdGC0LLQu9GP0Y7RgiDQv9GA0L7QstC10YDQutC4LjxwPg0KPGJyPg0K0J/QviDR
|
|
||||||
gNC10LfRg9C70YzRgtCw0YLQsNC8INGN0LvQtdC60YLRgNC+0L3QvdC+0LPQviDQsNGD0LrRhtC4
|
|
||||||
0L7QvdCwINC30LDQutC70Y7Rh9Cw0YLRjCDQutC+0L3RgtGA0LDQutGCINC90LAg0LHRg9C80LDQ
|
|
||||||
s9C1INC90LUg0L3Rg9C20L3Qvjxicj4NCjxwPg0KMTIg0LDQstCz0YPRgdGC0LAgMjAxNjxicj4N
|
|
||||||
CtCS0LDQu9C10YDQuNGPINCX0LXQvdC+0LLQuNC90LA8cD4NCjxicj4NCtCf0L4g0YDQtdC30YPQ
|
|
||||||
u9GM0YLQsNGC0LDQvCDRjdC70LXQutGC0YDQvtC90L3QvtCz0L4g0LDRg9C60YbQuNC+0L3QsCDQ
|
|
||||||
t9Cw0LrQu9GO0YfQsNGC0Ywg0LrQvtC90YLRgNCw0LrRgiDQvdCwINCx0YPQvNCw0LPQtSDQvdC1
|
|
||||||
INC90YPQttC90L7QnNC40L3RjdC60L7QvdC+0LzRgNCw0LfQstC40YLQuNGPINGA0LDQt9GK0Y/R
|
|
||||||
gdC90LjQu9C+LCDRh9GC0L4g0L/QviDRgNC10LfRg9C70YzRgtCw0YLQsNC8INGN0LvQtdC60YLR
|
|
||||||
gNC+0L3QvdC+0LPQviDQsNGD0LrRhtC40L7QvdCwINC/0YDQuCDQvdCw0LvQuNGH0LjQuCDQt9Cw
|
|
||||||
0LrQu9GO0YfQtdC90L3QvtCz0L4g0LrQvtC90YLRgNCw0LrRgtCwINCyINGN0LvQtdC60YLRgNC+
|
|
||||||
0L3QvdC+0Lkg0YTQvtGA0LzQtSDQt9Cw0LrQu9GO0YfQsNGC0Ywg0LrQvtC90YLRgNCw0LrRgiDQ
|
|
||||||
tdGJ0LUg0Lgg0LIg0L/QuNGB0YzQvNC10L3QvdC+0Lkg0YTQvtGA0LzQtSDQvdCwINCx0YPQvNCw
|
|
||||||
0LbQvdC+0Lwg0L3QvtGB0LjRgtC10LvQtSDQvdC1INC90YPQttC90L4uINCS0LXQtNC+0LzRgdGC
|
|
||||||
0LLQviDQv9C+0LTRh9C10YDQutC90YPQu9C+LCDRh9GC0L4g0YLQsNC60LDRjyDQvdC10L7QsdGF
|
|
||||||
0L7QtNC40LzQvtGB0YLRjCDQvdC1INC/0YDQtdC00YPRgdC80L7RgtGA0LXQvdCwINC00LXQudGB
|
|
||||||
0YLQstGD0Y7RidC40Lwg0LfQsNC60L7QvdC+0LTQsNGC0LXQu9GM0YHRgtCy0L7QvCAo0L/QuNGB
|
|
||||||
0YzQvNC+INCc0LjQvdGN0LrQvtC90L7QvNGA0LDQt9Cy0LjRgtC40Y8g0KDQvtGB0YHQuNC4INC+
|
|
||||||
0YIgNSDQuNGO0LvRjyAyMDE2INCzLiDihJYg0JQyONC4LTE2ODcpLjxwPg0KPHA+DQrQodC10LPQ
|
|
||||||
vtC00L3RjyDQtNC70Y8g0YLQvtCz0L4sINGH0YLQvtCx0Ysg0LfQsNC60LvRjtGH0LjRgtGMINGN
|
|
||||||
0LvQtdC60YLRgNC+0L3QvdGL0Lkg0LrQvtC90YLRgNCw0LrRgiDQvdCwINGN0LvQtdC60YLRgNC+
|
|
||||||
0L3QvdC+0Lwg0LDRg9C60YbQuNC+0L3QtSwg0LXQs9C+INC90LXQvtCx0YXQvtC00LjQvNC+INGA
|
|
||||||
0LDQt9C80LXRgdGC0LjRgtGMINCyINC10LTQuNC90L7QuSDQuNC90YTQvtGA0LzQsNGG0LjQvtC9
|
|
||||||
0L3QvtC5INGB0LjRgdGC0LXQvNC1ICjQldCY0KEpLCDQv9GA0LjRh9C10Lwg0L7QvSDQtNC+0LvQ
|
|
||||||
ttC10L0g0LHRi9GC0Ywg0L/QvtC00L/QuNGB0LDQvSDRg9GB0LjQu9C10L3QvdC+0Lkg0Y3Qu9C1
|
|
||||||
0LrRgtGA0L7QvdC90L7QuSDQv9C+0LTQv9C40YHRjNGOINC70LjRhtCwLCDQuNC80LXRjtGJ0LXQ
|
|
||||||
s9C+INC/0YDQsNCy0L4g0LTQtdC50YHRgtCy0L7QstCw0YLRjCDQvtGCINC40LzQtdC90Lgg0LfQ
|
|
||||||
sNC60LDQt9GH0LjQutCwLiDQodC00LXQu9Cw0YLRjCDRjdGC0L4g0L3QtdC+0LHRhdC+0LTQuNC8
|
|
||||||
0L4g0LIg0YLQtdGH0LXQvdC40LUg0YLRgNC10YUg0YDQsNCx0L7Rh9C40YUg0LTQvdC10Lkg0YEg
|
|
||||||
0LTQsNGC0Ysg0YDQsNC30LzQtdGJ0LXQvdC40Y8g0L/RgNC+0LXQutGC0LAg0YLQvtCz0L4g0LbQ
|
|
||||||
tSDQutC+0L3RgtGA0LDQutGC0LAuPGJyPg0KPGJyPg0K0JIg0LrQsNC60LjRhSDRgdC70YPRh9Cw
|
|
||||||
0Y/RhSDQt9Cw0LrQsNC30YfQuNC6INC+0LHRj9C30LDQvSDQv9GA0L7QstC+0LTQuNGC0Ywg0Y3Q
|
|
||||||
u9C10LrRgtGA0L7QvdC90YvQuSDQsNGD0LrRhtC40L7QvT8g0KPQt9C90LDQudGC0LUg0LjQtyDQ
|
|
||||||
vNCw0YLQtdGA0LjQsNC70LAgItCj0YHQu9C+0LLQuNGPINC/0YDQuNC80LXQvdC10L3QuNGPINGN
|
|
||||||
0LvQtdC60YLRgNC+0L3QvdC+0LPQviDQsNGD0LrRhtC40L7QvdCwIiDQsiAi0K3QvdGG0LjQutC7
|
|
||||||
0L7Qv9C10LTQuNC4INGA0LXRiNC10L3QuNC5LiDQk9C+0YHRg9C00LDRgNGB0YLQstC10L3QvdGL
|
|
||||||
0LUg0Lgg0LrQvtGA0L/QvtGA0LDRgtC40LLQvdGL0LUg0LfQsNC60YPQv9C60LgiINC40L3RgtC1
|
|
||||||
0YDQvdC10YIt0LLQtdGA0YHQuNC4INGB0LjRgdGC0LXQvNGLINCT0JDQoNCQ0J3Qoi4g0J/QvtC7
|
|
||||||
0YPRh9C40YLQtSDQv9C+0LvQvdGL0Lkg0LTQvtGB0YLRg9C/INC90LAgMyDQtNC90Y8g0LHQtdGB
|
|
||||||
0L/Qu9Cw0YLQvdC+ITxwPg0K0J/QvtC70YPRh9C40YLRjCDQtNC+0YHRgtGD0L88cD4NCtCj0LrQ
|
|
||||||
sNC30LDQvdC90YvQuSDQv9GA0L7QtdC60YIg0L/RgNC4INGN0YLQvtC8INGC0L7QttC1INC00L7Q
|
|
||||||
u9C20LXQvSDQsdGL0YLRjCDQv9C+0LTQv9C40YHQsNC9INGD0YHQuNC70LXQvdC90L7QuSDRjdC7
|
|
||||||
0LXQutGC0YDQvtC90L3QvtC5INC/0L7QtNC/0LjRgdGM0Y4sINC90L4g0YPQttC1INC70LjRhtCw
|
|
||||||
LCDQutC+0YLQvtGA0L7QtSDQuNC80LXQtdGCINC/0YDQsNCy0L4g0LTQtdC50YHRgtCy0L7QstCw
|
|
||||||
0YLRjCDQvtGCINC40LzQtdC90Lgg0L/QvtCx0LXQtNC40YLQtdC70Y8g0Y3Qu9C10LrRgtGA0L7Q
|
|
||||||
vdC90L7Qs9C+INCw0YPQutGG0LjQvtC90LAuINCf0L7QvNC40LzQviDQv9GA0L7QtdC60YLQsCDQ
|
|
||||||
utC+0L3RgtGA0LDQutGC0LAg0YLQsNC60L7QuSDQv9C+0LHQtdC00LjRgtC10LvRjCDQtNC+0LvQ
|
|
||||||
ttC10L0g0L/RgNC10LTQvtGB0YLQsNCy0LjRgtGMINC+0LHQtdGB0L/QtdGH0LXQvdC40LUg0LjR
|
|
||||||
gdC/0L7Qu9C90LXQvdC40Y8g0LrQvtC90YLRgNCw0LrRgtCwICjRhy4gNyDRgdGCLiA3MCDQpNC1
|
|
||||||
0LTQtdGA0LDQu9GM0L3QvtCz0L4g0LfQsNC60L7QvdCwINC+0YIgNSDQsNC/0YDQtdC70Y8gMjAx
|
|
||||||
MyDQsy4g4oSWIDQ0LdCk0JcgItCeINC60L7QvdGC0YDQsNC60YLQvdC+0Lkg0YHQuNGB0YLQtdC8
|
|
||||||
0LUg0LIg0YHRhNC10YDQtSDQt9Cw0LrRg9C/0L7QuiDRgtC+0LLQsNGA0L7Qsiwg0YDQsNCx0L7R
|
|
||||||
giwg0YPRgdC70YPQsyDQtNC70Y8g0L7QsdC10YHQv9C10YfQtdC90LjRjyDQs9C+0YHRg9C00LDR
|
|
||||||
gNGB0YLQstC10L3QvdGL0YUg0Lgg0LzRg9C90LjRhtC40L/QsNC70YzQvdGL0YUg0L3Rg9C20LQi
|
|
||||||
OyDQtNCw0LvQtdC1IOKAkyDQl9Cw0LrQvtC9IOKEliA0NC3QpNCXKS48cD4NCjxicj4NCtChINC8
|
|
||||||
0L7QvNC10L3RgtCwINGA0LDQt9C80LXRidC10L3QuNGPINCyINCV0JjQoSDQv9C+0LTQv9C40YHQ
|
|
||||||
sNC90L3QvtCz0L4g0LfQsNC60LDQt9GH0LjQutC+0Lwg0LrQvtC90YLRgNCw0LrRgtCwINC+0L0g
|
|
||||||
0YHRh9C40YLQsNC10YLRgdGPINC30LDQutC70Y7Rh9C10L3QvdGL0LwgKNGHLiA4INGB0YIuIDcw
|
|
||||||
INCX0LDQutC+0L3QsCDihJYgNDQt0KTQlykuPGJyPg0KPGJyPg0K0J3QsNC/0L7QvNC90LjQvCwg
|
|
||||||
0L/QviDQtNC10LnRgdGC0LLRg9GO0YnQtdC80YMg0LfQsNC60L7QvdC+0LTQsNGC0LXQu9GM0YHR
|
|
||||||
gtCy0YMsINGN0LvQtdC60YLRgNC+0L3QvdGL0Lkg0LDRg9C60YbQuNC+0L0g0L/RgNC+0LLQvtC0
|
|
||||||
0LjRgtGB0Y8g0L/Rg9GC0LXQvCDRgdC90LjQttC10L3QuNGPINC90LDRh9Cw0LvRjNC90L7QuSAo
|
|
||||||
0LzQsNC60YHQuNC80LDQu9GM0L3QvtC5KSDRhtC10L3RiyDQutC+0L3RgtGA0LDQutGC0LAsINGD
|
|
||||||
0LrQsNC30LDQvdC90L7QuSDQsiDQuNC30LLQtdGJ0LXQvdC40Lgg0L4g0L/RgNC+0LLQtdC00LXQ
|
|
||||||
vdC40Lgg0YLQsNC60L7Qs9C+INCw0YPQutGG0LjQvtC90LAuINCSINC90LXQvCDQvNC+0LPRg9GC
|
|
||||||
INGD0YfQsNGB0YLQstC+0LLQsNGC0Ywg0YLQvtC70YzQutC+INCw0LrQutGA0LXQtNC40YLQvtCy
|
|
||||||
0LDQvdC90YvQtSDRg9GH0LDRgdGC0L3QuNC60LgsINCwINC80LXRgdGC0L7QvCDQtdCz0L4g0L/R
|
|
||||||
gNC+0LLQtdC00LXQvdC40Y8g0Y/QstC70Y/QtdGC0YHRjyDRjdC70LXQutGC0YDQvtC90L3QsNGP
|
|
||||||
INC/0LvQvtGJ0LDQtNC60LAgKNGB0YIuIDY4INCX0LDQutC+0L3QsCDihJYgNDQt0KTQlykuPGJy
|
|
||||||
Pg0KPHA+DQo1INC00LXQutCw0LHRgNGPIDIwMTQg0LPQvtC00LAg0YHQvtGB0YLQvtGP0LvQvtGB
|
|
||||||
0Ywg0LjQvdGC0LXRgNC90LXRgi3QuNC90YLQtdGA0LLRjNGOINGBINC90LDRh9Cw0LvRjNC90LjQ
|
|
||||||
utC+0Lwg0KPQv9GA0LDQstC70LXQvdC40Y8g0LrQsNC80LXRgNCw0LvRjNC90L7Qs9C+INC60L7Q
|
|
||||||
vdGC0YDQvtC70Y8g0KTQtdC00LXRgNCw0LvRjNC90L7QuSDQvdCw0LvQvtCz0L7QstC+0Lkg0YHQ
|
|
||||||
u9GD0LbQsdGLINCh0LDRgtC40L3Ri9C8INCU0LzQuNGC0YDQuNC10Lwg0KHRgtCw0L3QuNGB0LvQ
|
|
||||||
sNCy0L7QstC40YfQtdC8Ljxicj4NCjxicj4NCtCi0LXQvNCwINC40L3RgtC10YDQvdC10YIt0LjQ
|
|
||||||
vdGC0LXRgNCy0YzRjjogItCd0LDQu9C+0LMg0L3QsCDQtNC+0LHQsNCy0LvQtdC90L3Rg9GOINGB
|
|
||||||
0YLQvtC40LzQvtGB0YLRjDog0L3QvtCy0LDRhtC40LggMjAxNSDQs9C+0LTQsCIuPGJyPg0KPHA+
|
|
||||||
DQrQktC10LTRg9GJ0LDRjzog0JTQvtCx0YDRi9C5INC00LXQvdGMLCDQlNC80LjRgtGA0LjQuSDQ
|
|
||||||
odGC0LDQvdC40YHQu9Cw0LLQvtCy0LjRhyEg0KDQsNGB0YHQutCw0LbQuNGC0LUsINC/0L7QttCw
|
|
||||||
0LvRg9C50YHRgtCwLCDQutCw0LrQuNC1INC90L7QstCw0YbQuNC4LCDRgdCy0Y/Qt9Cw0L3QvdGL
|
|
||||||
0LUg0YEg0L/RgNC10LTRgdGC0LDQstC70LXQvdC40LXQvCDQvtGC0YfQtdGC0L3QvtGB0YLQuCDQ
|
|
||||||
v9C+INCd0JTQoSwg0L7QttC40LTQsNGO0YIg0L3QsNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC4
|
|
||||||
0LrQvtCyINCyIDIwMTUg0LPQvtC00YM/PGJyPg0KPGJyPg0K0KHQsNGC0LjQvSDQlC7QoS46INCU
|
|
||||||
0L7QsdGA0YvQuSDQtNC10L3RjCEg0J3QsNGH0LjQvdCw0Y8g0YEg0L3QsNC70L7Qs9C+0LLQvtCz
|
|
||||||
0L4g0L/QtdGA0LjQvtC00LAg0LfQsCAxINC60LLQsNGA0YLQsNC7IDIwMTUg0LPQvtC00LAg0L3Q
|
|
||||||
sCDQvtGB0L3QvtCy0LDQvdC40Lgg0L/Rg9C90LrRgtCwIDUuMSDRgdGC0LDRgtGM0LggMTc0INCa
|
|
||||||
0L7QtNC10LrRgdCwICjQsiDRgNC10LTQsNC60YbQuNC4INCk0LXQtNC10YDQsNC70YzQvdC+0LPQ
|
|
||||||
viDQt9Cw0LrQvtC90LAg0L7RgiAyOC4wNi4yMDEzIOKEliAxMzQt0KTQlykg0LIg0L3QsNC70L7Q
|
|
||||||
s9C+0LLRg9GOINC00LXQutC70LDRgNCw0YbQuNGOINC/0L4g0J3QlNChINCy0LrQu9GO0YfQsNGO
|
|
||||||
0YLRgdGPINGB0LLQtdC00LXQvdC40Y8sINGD0LrQsNC30LDQvdC90YvQtSDQsiDQutC90LjQs9C1
|
|
||||||
INC/0L7QutGD0L/QvtC6INC4INC60L3QuNCz0LUg0L/RgNC+0LTQsNC2LiDQn9GA0Lgg0L7RgdGD
|
|
||||||
0YnQtdGB0YLQstC70LXQvdC40Lgg0L/QvtGB0YDQtdC00L3QuNGH0LXRgdC60L7QuSDQtNC10Y/R
|
|
||||||
gtC10LvRjNC90L7RgdGC0Lgg0LIg0L3QsNC70L7Qs9C+0LLRg9GOINC00LXQutC70LDRgNCw0YbQ
|
|
||||||
uNGOINC/0L4g0J3QlNChINCy0LrQu9GO0YfQsNGO0YLRgdGPINGB0LLQtdC00LXQvdC40Y8sINGD
|
|
||||||
0LrQsNC30LDQvdC90YvQtSDQsiDQttGD0YDQvdCw0LvQtSDRg9GH0LXRgtCwINC/0L7Qu9GD0YfQ
|
|
||||||
tdC90L3Ri9GFINC4INCy0YvRgdGC0LDQstC70LXQvdC90YvRhSDRgdGH0LXRgtC+0LIt0YTQsNC6
|
|
||||||
0YLRg9GALCDQsiDQvtGC0L3QvtGI0LXQvdC40Lgg0YPQutCw0LfQsNC90L3QvtC5INC00LXRj9GC
|
|
||||||
0LXQu9GM0L3QvtGB0YLQuC48YnI+DQo8YnI+DQrQn9GA0LjQutCw0LfQvtC8INCk0J3QoSDQoNC+
|
|
||||||
0YHRgdC40Lgg0L7RgiAyOS4xMC4yMDE0IOKEliDQnNCc0JItNy0zLzU1OEAg0YPRgtCy0LXRgNC2
|
|
||||||
0LTQtdC90LAg0YTQvtGA0LzQsCDQvdCw0LvQvtCz0L7QstC+0Lkg0LTQtdC60LvQsNGA0LDRhtC4
|
|
||||||
0Lgg0L/QviDQndCU0KEsINC/0L7RgNGP0LTQvtC6INC10LUg0LfQsNC/0L7Qu9C90LXQvdC40Y8g
|
|
||||||
0Lgg0YTQvtGA0LzQsNGCINC/0YDQtdC00YHRgtCw0LLQu9C10L3QuNGPINCyINGN0LvQtdC60YLR
|
|
||||||
gNC+0L3QvdC+0Lkg0YTQvtGA0LzQtS4g0JIg0L3QsNGB0YLQvtGP0YnQtdC1INCy0YDQtdC80Y8g
|
|
||||||
0L/RgNC40LrQsNC3INC/0YDQvtGF0L7QtNC40YIg0LPQvtGB0YPQtNCw0YDRgdGC0LLQtdC90L3R
|
|
||||||
g9GOINGA0LXQs9C40YHRgtGA0LDRhtC40Y4g0LIg0JzQuNC90Y7RgdGC0LUg0KDQvtGB0YHQuNC4
|
|
||||||
LjxwPg0KPHA+DQrQkiDQvdC+0LLQvtC5INGE0L7RgNC80LUg0L3QsNC70L7Qs9C+0LLQvtC5INC0
|
|
||||||
0LXQutC70LDRgNCw0YbQuNC4INC/0L4g0J3QlNChINC/0YDQtdC00YPRgdC80L7RgtGA0LXQvdGL
|
|
||||||
INGA0LDQt9C00LXQu9GLLCDRgdC+0LTQtdGA0LbQsNGJ0LjQtSDRgdCy0LXQtNC10L3QuNGPINC4
|
|
||||||
0Lcg0LrQvdC40LMg0L/QvtC60YPQv9C+0LosINC60L3QuNCzINC/0YDQvtC00LDQtiwg0LbRg9GA
|
|
||||||
0L3QsNC70L7QsiDRg9GH0LXRgtCwINC/0L7Qu9GD0YfQtdC90L3Ri9GFINC4INCy0YvRgdGC0LDQ
|
|
||||||
stC70LXQvdC90YvRhSDRgdGH0LXRgtC+0LIt0YTQsNC60YLRg9GALjxicj4NCjxicj4NCtCSINGB
|
|
||||||
0L7QvtGC0LLQtdGC0YHRgtCy0LjQuCDRgSDQv9GD0L3QutGC0L7QvCAzINGB0YLQsNGC0YzQuCA4
|
|
||||||
MCDQuCDQv9GD0L3QutGC0L7QvCA1INGB0YLQsNGC0YzQuCAxNzQg0JrQvtC00LXQutGB0LAg0L3Q
|
|
||||||
sNC70L7Qs9C+0LLQsNGPINC00LXQutC70LDRgNCw0YbQuNGPINC/0L4g0J3QlNChINC00L7Qu9C2
|
|
||||||
0L3QsCDQv9GA0LXQtNGB0YLQsNCy0LvRj9GC0YzRgdGPINCyINGN0LvQtdC60YLRgNC+0L3QvdC+
|
|
||||||
0Lkg0YTQvtGA0LzQtSDQv9C+INGC0LXQu9C10LrQvtC80LzRg9C90LjQutCw0YbQuNC+0L3QvdGL
|
|
||||||
0Lwg0LrQsNC90LDQu9Cw0Lwg0YHQstGP0LfQuCDRh9C10YDQtdC3INC+0L/QtdGA0LDRgtC+0YDQ
|
|
||||||
sCDRjdC70LXQutGC0YDQvtC90L3QvtCz0L4g0LTQvtC60YPQvNC10L3RgtC+0L7QsdC+0YDQvtGC
|
|
||||||
0LAuINCi0LDQutC40Lwg0L7QsdGA0LDQt9C+0LwsINGE0L7RgNC80LjRgNC+0LLQsNC90LjQtSDQ
|
|
||||||
vdCw0LvQvtCz0L7QstC+0Lkg0LTQtdC60LvQsNGA0LDRhtC40Lgg0L/QviDQndCU0KEg0L3QsCDQ
|
|
||||||
sdGD0LzQsNC20L3Ri9GFINC90L7RgdC40YLQtdC70Y/RhSDQt9Cw0LrQvtC90L7QtNCw0YLQtdC7
|
|
||||||
0YzRgdGC0LLQvtC8INC+INC90LDQu9C+0LPQsNGFINC4INGB0LHQvtGA0LDRhSDQvdC1INC/0YDQ
|
|
||||||
tdC00YPRgdC80L7RgtGA0LXQvdC+Ljxicj4NCjxicj4NCtCb0LjRhtCwLCDQvdC1INGP0LLQu9GP
|
|
||||||
0Y7RidC40LXRgdGPINC90LDQu9C+0LPQvtC/0LvQsNGC0LXQu9GM0YnQuNC60LDQvNC4INCd0JTQ
|
|
||||||
oSDQuNC70Lgg0L3QsNC70L7Qs9C+0LLRi9C80Lgg0LDQs9C10L3RgtCw0LzQuCDQv9C+INCd0JTQ
|
|
||||||
oSwg0L3QviDQvtGB0YPRidC10YHRgtCy0LvRj9GO0YnQuNC1INC/0L7RgdGA0LXQtNC90LjRh9C1
|
|
||||||
0YHQutGD0Y4g0LTQtdGP0YLQtdC70YzQvdC+0YHRgtGMLCDQtNC+0LvQttC90Ysg0L3QsCDQvtGB
|
|
||||||
0L3QvtCy0LDQvdC40Lgg0L/Rg9C90LrRgtCwIDUuMiDRgdGC0LDRgtGM0LggMTc0INCa0L7QtNC1
|
|
||||||
0LrRgdCwINC/0YDQtdC00YHRgtCw0LLQu9GP0YLRjCDQsiDQvdCw0LvQvtCz0L7QstGL0Lkg0L7R
|
|
||||||
gNCz0LDQvSDQsiDQvtGC0L3QvtGI0LXQvdC40Lgg0YPQutCw0LfQsNC90L3QvtC5INC00LXRj9GC
|
|
||||||
0LXQu9GM0L3QvtGB0YLQuCDQttGD0YDQvdCw0Lsg0YPRh9C10YLQsCDQv9C+0LvRg9GH0LXQvdC9
|
|
||||||
0YvRhSDQuCDQstGL0YHRgtCw0LLQu9C10L3QvdGL0YUg0YHRh9C10YLQvtCyLdGE0LDQutGC0YPR
|
|
||||||
gCDQv9C+INCi0JrQoSDRh9C10YDQtdC3INC+0L/QtdGA0LDRgtC+0YDQsCDQrdCU0J4uPHA+DQo8
|
|
||||||
cD4NCtCk0LXQtNC10YDQsNC70YzQvdC+0Lkg0L3QsNC70L7Qs9C+0LLQvtC5INGB0LvRg9C20LHQ
|
|
||||||
vtC5INGA0LDQt9GA0LDQsdC+0YLQsNC90LAg0LHQtdGB0L/Qu9Cw0YLQvdCw0Y8g0L/RgNC+0LPR
|
|
||||||
gNCw0LzQvNCwICLQndCw0LvQvtCz0L7Qv9C70LDRgtC10LvRjNGJ0LjQuiDQrtCbIiDQtNC70Y8g
|
|
||||||
0LLQtdC00LXQvdC40Y8g0LrQvdC40LMg0L/QvtC60YPQv9C+0Log0Lgg0L/RgNC+0LTQsNC2LCDQ
|
|
||||||
sCDRgtCw0LrQttC1INGE0L7RgNC80LjRgNC+0LLQsNC90LjRjyDQvdCw0LvQvtCz0L7QstC+0Lkg
|
|
||||||
0LTQtdC60LvQsNGA0LDRhtC40Lgg0L/QviDQndCU0KEuINCU0LDQvdC90YvQvCDQv9GA0L7Qs9GA
|
|
||||||
0LDQvNC80L3Ri9C8INC/0YDQvtC00YPQutGC0L7QvCDQvNC+0LbQtdGCINCy0L7RgdC/0L7Qu9GM
|
|
||||||
0LfQvtCy0LDRgtGM0YHRjyDQu9GO0LHQvtC5INC90LDQu9C+0LPQvtC/0LvQsNGC0LXQu9GM0YnQ
|
|
||||||
uNC6INCx0LXRgdC/0LvQsNGC0L3QviDQt9Cw0LPRgNGD0LfQuNCyINC10LPQviDRgSDQvtGE0LjR
|
|
||||||
htC40LDQu9GM0L3QvtCz0L4g0YHQsNC50YLQsCDQpNCd0KEg0KDQvtGB0YHQuNC4IChodHRwOi8v
|
|
||||||
d3d3Lm5hbG9nLnJ1L3JuNzcvL3Byb2dyYW0vYWxsL25hbF91bC8pLjxicj4NCjxicj4NCtCS0LXQ
|
|
||||||
tNGD0YnQsNGPOiDQlNC80LjRgtGA0LjQuSDQodGC0LDQvdC40YHQu9Cw0LLQvtCy0LjRhywg0LXR
|
|
||||||
gdGC0Ywg0YLQsNC60LDRjyDRgdC40YLRg9Cw0YbQuNGPOiDQtNC+0LHRgNC+0YHQvtCy0LXRgdGC
|
|
||||||
0L3QsNGPINC60L7QvNC/0LDQvdC40Y8g0L7RgtGA0LDQt9C40LvQsCDQsiDQtNC10LrQu9Cw0YDQ
|
|
||||||
sNGG0LjQuCDRgNGP0LQg0YHRh9C10YLQvtCyLdGE0LDQutGC0YPRgCwg0L/QviDQutC+0YLQvtGA
|
|
||||||
0YvQvCDQv9C+0YHRgtCw0LLRidC40Log0L7RgtGH0LXRgtC90L7RgdGC0Ywg0L3QtSDRgdC00LDQ
|
|
||||||
uyDQstC+0L7QsdGJ0LUuINCl0L7RgtGPINC90LAg0LzQvtC80LXQvdGCINC/0YDQvtCy0LXQtNC1
|
|
||||||
0L3QuNGPINGB0LTQtdC70LrQuCDQutC+0L3RgtGA0LDQs9C10L3RgiDQv9C+INC00LDQvdC90YvQ
|
|
||||||
vCDQstGL0L/QuNGB0LrQuCDQuNC3INCV0JPQoNCu0Jsg0LHRi9C7INCy0L/QvtC70L3QtSDQtNC1
|
|
||||||
0LnRgdGC0LLRg9GO0YnQuNC8LiDQmtCw0LrQuNC1INC/0L7RgdC70LXQtNGB0YLQstC40Y8g0L7Q
|
|
||||||
ttC40LTQsNGO0YIg0L3QsNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC40LrQsCDQsiDRgtCw0LrQ
|
|
||||||
vtC8INGB0LvRg9GH0LDQtT8gPHA+DQo8YnI+DQrQodCw0YLQuNC9INCULtChLjog0JXRgdC70Lgg
|
|
||||||
0LrQvtC90YLRgNCw0LPQtdC90YIg0L3QsNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC40LrQsCDQ
|
|
||||||
vdC1INC/0YDQtdC00YHRgtCw0LLQuNC7INC90LDQu9C+0LPQvtCy0YPRjiDQtNC10LrQu9Cw0YDQ
|
|
||||||
sNGG0LjRjiDQv9C+INCd0JTQoSDQuNC70Lgg0LIg0LXQs9C+INC/0YDQtdC00YHRgtCw0LLQu9C1
|
|
||||||
0L3QvdC+0Lkg0LTQtdC60LvQsNGA0LDRhtC40Lgg0L3QtSDQvtGC0YDQsNC20LXQvdGLINC60LDQ
|
|
||||||
utC40LUt0LvQuNCx0L4g0YHRh9C10YLQsC3RhNCw0LrRgtGD0YDRiywg0YLQviDRgtCw0LrQvtC5
|
|
||||||
INC60L7QvdGC0YDQsNCz0LXQvdGCINGB0YLQsNC90L7QstC40YLRgdGPINC+0LHRitC10LrRgtC+
|
|
||||||
0Lwg0LLQvdC40LzQsNC90LjRjyDRgdC+INGB0YLQvtGA0L7QvdGLINC90LDQu9C+0LPQvtCy0L7Q
|
|
||||||
s9C+INC+0YDQs9Cw0L3QsC4g0J3QsCDQv9C10YDQstC+0Lwg0Y3RgtCw0L/QtSDQsiDRgdC70YPR
|
|
||||||
h9Cw0LUg0L7RgtGB0YPRgtGB0YLQstC40Y8g0L3QsNC70L7Qs9C+0LLQvtC5INC00LXQutC70LDR
|
|
||||||
gNCw0YbQuNC4INC90LDQu9C+0LPQvtCy0YvQuSDQvtGA0LPQsNC9INCx0YPQtNC10YIg0LjQvNC1
|
|
||||||
0YLRjCDQstC+0LfQvNC+0LbQvdC+0YHRgtGMINC/0YDQuNC+0YHRgtCw0L3QvtCy0LjRgtGMINC0
|
|
||||||
0LLQuNC20LXQvdC40LUg0LTQtdC90LXQttC90YvRhSDRgdGA0LXQtNGB0YLQsiDQv9C+INGA0LDR
|
|
||||||
gdGH0LXRgtC90YvQvCDRgdGH0LXRgtCw0LwuINCU0LDQu9C10LUg0L/RgNC+0LLQvtC00LjRgtGB
|
|
||||||
0Y8g0YPRgdGC0LDQvdC+0LLQu9C10L3QvdGL0Lkg0LPQu9Cw0LLQvtC5IDE0INCd0LDQu9C+0LPQ
|
|
||||||
vtCy0L7Qs9C+INC60L7QtNC10LrRgdCwINCg0L7RgdGB0LjQudGB0LrQvtC5INCk0LXQtNC10YDQ
|
|
||||||
sNGG0LjQuCDQutC+0LzQv9C70LXQutGBINC60L7QvdGC0YDQvtC70YzQvdGL0YUg0LzQtdGA0L7Q
|
|
||||||
v9GA0LjRj9GC0LjQuSDQv9C+INGE0L7RgNC80LjRgNC+0LLQsNC90LjRjiDQtNC+0LrQsNC30LDR
|
|
||||||
gtC10LvRjNC90L7QuSDQsdCw0LfRiyDQvtCxINGD0LrQu9C+0L3QtdC90LjQuCDQvtGCINC90LDQ
|
|
||||||
u9C+0LPQvtC+0LHQu9C+0LbQtdC90LjRjy4g0K3RgtC+INC40YHRgtGA0LXQsdC+0LLQsNC90LjQ
|
|
||||||
tSDQuNC90YTQvtGA0LzQsNGG0LjQuCDQuCDQtNC+0LrRg9C80LXQvdGC0L7Qsiwg0LAg0L/RgNC4
|
|
||||||
INC90LXQvtCx0YXQvtC00LjQvNC+0YHRgtC4INC00YDRg9Cz0LjQtSDQvNC10YDQvtC/0YDQuNGP
|
|
||||||
0YLQuNGPINC90LDQu9C+0LPQvtCy0L7Qs9C+INC60L7QvdGC0YDQvtC70Y8uINCf0YDQuCDRjdGC
|
|
||||||
0L7QvCDQsiDRgdC70YPRh9Cw0LUg0YPRgdGC0LDQvdC+0LLQu9C10L3QuNGPINGE0LDQutGC0LAg
|
|
||||||
0YDQtdCw0LvRjNC90L7RgdGC0Lgg0L7Qv9C10YDQsNGG0LjQuCwg0LTQu9GPINGB0LDQvNC+0LPQ
|
|
||||||
viDQvdCw0LvQvtCz0L7Qv9C70LDRgtC10LvRjNGJ0LjQutCwINC90Lgg0LrQsNC60LjRhSDQv9C+
|
|
||||||
0YHQu9C10LTRgdGC0LLQuNC5INC90LUg0LHRg9C00LXRgi4g0Jog0L7RgtCy0LXRgtGB0YLQstC1
|
|
||||||
0L3QvdC+0YHRgtC4INCx0YPQtNC10YIg0L/RgNC40LLQu9C10YfQtdC90L4g0YLQviDQu9C40YbQ
|
|
||||||
viwg0LrQvtGC0L7RgNC+0LUg0L3QsNGA0YPRiNC40LvQviDQvdCw0LvQvtCz0L7QstC+0LUg0LfQ
|
|
||||||
sNC60L7QvdC+0LTQsNGC0LXQu9GM0YHRgtCy0L4uPGJyPg0KPHA+DQrQn9GA0Lgg0Y3RgtC+0Lwg
|
|
||||||
0LzRiyDRgNC10LrQvtC80LXQvdC00YPQtdC8INCw0LrQutGD0YDQsNGC0L3QviDQv9C+0LTRhdC+
|
|
||||||
0LTQuNGC0Ywg0Log0LLRi9Cx0L7RgNGDINC/0L7RgtC10L3RhtC40LDQu9GM0L3Ri9GFINC60L7Q
|
|
||||||
vdGC0YDQsNCz0LXQvdGC0L7QsiDQuCDQv9GA0L7Rj9Cy0LvRj9GC0Ywg0LTQvtC70LbQvdGD0Y4g
|
|
||||||
0L7RgdC80L7RgtGA0LjRgtC10LvRjNC90L7RgdGC0YwsINGC0LDQuiDQutCw0Log0Y3RgtC+INC9
|
|
||||||
0LXQvtCx0YXQvtC00LjQvNC+INC00LvRjyDRgdC+0YXRgNCw0L3QtdC90LjRjyDQtNC10LvQvtCy
|
|
||||||
0L7QuSDRgNC10L/Rg9GC0LDRhtC40Lgg0Lgg0YTQvtGA0LzQuNGA0L7QstCw0L3QuNGPINC/0L7Q
|
|
||||||
u9C+0LbQuNGC0LXQu9GM0L3QvtC5INC90LDQu9C+0LPQvtCy0L7QuSDQuNGB0YLQvtGA0LjQuC48
|
|
||||||
cD4NCjxicj4NCtCS0LXQtNGD0YnQsNGPOiDQldGB0LvQuCDQsiDRgdCy0LXQtNC10L3QuNGP0YUg
|
|
||||||
0YMg0LrQvtC90YLRgNCw0LPQtdC90YLQvtCyINCx0YPQtNGD0YIg0YDQsNGB0YXQvtC20LTQtdC9
|
|
||||||
0LjRjywg0L3QsNC/0YDQuNC80LXRgCwg0YDQsNC30L3Ri9C1INC90L7QvNC10YDQsCDRgdGH0LXR
|
|
||||||
gtC+0LIt0YTQsNC60YLRg9GAINC4INGA0LDQt9C90YvQtSDQtNCw0YLRiywg0LrQsNC6INGN0YLQ
|
|
||||||
viDQsdGD0LTRg9GCINC+0YbQtdC90LjQstCw0YLRjCDQuNC90YHQv9C10LrRgtC+0YDRiz8g0JAg
|
|
||||||
0LXRgdC70Lgg0L7RgtC70LjRh9Cw0Y7RgtGB0Y8g0YHRg9C80LzRiyDQv9C+INC30LDRgNC10LPQ
|
|
||||||
uNGB0YLRgNC40YDQvtCy0LDQvdC90YvQvCDRgdGH0LXRgtCw0Lwt0YTQsNC60YLRg9GA0LDQvD8g
|
|
||||||
PGJyPg0KPGJyPg0K0KHQsNGC0LjQvSDQlC7QoS46INCf0YDQtdC20LTQtSDQstGB0LXQs9C+INCy
|
|
||||||
0L4g0LjQt9Cx0LXQttCw0L3QuNC1INGC0LXRhdC90LjRh9C10YHQutC40YUg0L7RiNC40LHQvtC6
|
|
||||||
INCyINC/0YDQtdC00YHRgtCw0LLQu9C10L3QvdC+0Lkg0LTQtdC60LvQsNGA0LDRhtC40Lgg0L/Q
|
|
||||||
viDQndCU0KEg0LzRiyDRgNC10LrQvtC80LXQvdC00YPQtdC8INC/0YDQvtCy0LXRgNC40YLRjCDQ
|
|
||||||
uNC90YTQvtGA0LzQsNGG0LjRjiDQviDQutC+0L3RgtGA0LDQs9C10L3RgtCw0YUsINC60L7RgtC+
|
|
||||||
0YDQsNGPINGB0L7QtNC10YDQttC40YLRgdGPINCyINCy0LDRiNC10Lkg0YPRh9C10YLQvdC+0Lkg
|
|
||||||
KNCx0YPRhdCz0LDQu9GC0LXRgNGB0LrQvtC5KSDRgdC40YHRgtC10LzQtSwg0L3QsCDQv9GA0LXQ
|
|
||||||
tNC80LXRgiDQv9GA0LDQstC40LvRjNC90L7RgdGC0Lgg0LfQsNC90LXRgdC10L3QuNGPINCyINGB
|
|
||||||
0LjRgdGC0LXQvNGDINCY0J3QnSDQuCDQmtCf0J8g0LrQvtC90YLRgNCw0LPQtdC90YLQvtCyLiDQ
|
|
||||||
nNC+0LbQvdC+INCy0L7RgdC/0L7Qu9GM0LfQvtCy0LDRgtGM0YHRjywg0L3QsNC/0YDQuNC80LXR
|
|
||||||
gCwg0L7QvdC70LDQudC9LdGB0LXRgNCy0LjRgdC+0LwsINGA0LDQt9C80LXRidC10L3QvdGL0Lwg
|
|
||||||
0L3QsCDQvtGE0LjRhtC40LDQu9GM0L3QvtC8INGB0LDQudGC0LUg0KTQndChINCg0L7RgdGB0LjQ
|
|
||||||
uCAod3d3Lm5hbG9nLnJ1LCBodHRwOi8vbnBjaGsubmFsb2cucnUpLjxicj4NCjxicj4NCtCV0YHQ
|
|
||||||
u9C4INGA0LDRgdGF0L7QttC00LXQvdC40Y8g0LIg0YHQstC10LTQtdC90LjRj9GFINC+0LEg0L7Q
|
|
||||||
v9C10YDQsNGG0LjRj9GFINC60L7QvdGC0YDQsNCz0LXQvdGC0L7QsiDQsdGD0LTRg9GCINCy0YHQ
|
|
||||||
tS3RgtCw0LrQuCDQstGL0Y/QstC70LXQvdGLLCDRgtC+INC80Ysg0LjRhSDQtNC10LvQuNC8INC9
|
|
||||||
0LAg0LTQstC1INC60LDRgtC10LPQvtGA0LjQuC4g0J/QtdGA0LLQsNGPINGN0YLQviDRgtC10YXQ
|
|
||||||
vdC40YfQtdGB0LrQuNC1INC+0YjQuNCx0LrQuCwg0YLQsNC6INC90LDQt9GL0LLQsNC10LzRi9C1
|
|
||||||
ICLQvtGI0LjQsdC60Lgg0LLQstC+0LTQsCIsINC60L7RgtC+0YDRi9C1INC90LUg0LjQvNC10Y7R
|
|
||||||
giDQstGL0YHQvtC60L7Qs9C+INC/0YDQuNC+0YDQuNGC0LXRgtCwINCyINC+0YLRgNCw0LHQvtGC
|
|
||||||
0LrQtS4g0J7RiNC40LHQutC4INCyINC90L7QvNC10YDQtSwg0LTQsNGC0LUg0YHRh9C10YLQsC3R
|
|
||||||
hNCw0LrRgtGD0YDRiyDQuNC70Lgg0LIg0LTRgNGD0LPQvtC8INGA0LXQutCy0LjQt9C40YLQtSwg
|
|
||||||
0L3QtSDQstC70LjRj9GO0YnQtdC8INC90LAg0YHRg9C80LzRgyDQvdCw0LvQvtCz0LAsINC80Ysg
|
|
||||||
0YDQsNGB0YHRh9C40YLRi9Cy0LDQtdC8INGD0YHRgtGA0LDQvdGP0YLRjCDQvdCwINGN0YLQsNC/
|
|
||||||
0LUg0LfQsNC/0YDQvtGB0LAg0L/QvtGP0YHQvdC10L3QuNC5LiDQn9GA0Lgg0Y3RgtC+0Lwg0L3Q
|
|
||||||
sNC70L7Qs9C+0L/Qu9Cw0YLQtdC70YzRidC40LrRgyDQv9GA0LXQtNGB0YLQsNCy0LjRgtGB0Y8g
|
|
||||||
0LLQvtC30LzQvtC20L3QvtGB0YLRjCDQvdCw0L/RgNCw0LLQuNGC0Ywg0LIg0L3QsNC70L7Qs9C+
|
|
||||||
0LLRi9C5INC+0YDQs9Cw0L0g0YTQvtGA0LzQsNC70LjQt9C+0LLQsNC90L3Ri9C1INC/0L7Rj9GB
|
|
||||||
0L3QtdC90LjRjyDQsdC10Lcg0LrQsNC60LjRhS3Qu9C40LHQviDQvdCw0LvQvtCz0L7QstGL0YUg
|
|
||||||
0L/QvtGB0LvQtdC00YHRgtCy0LjQuS4g0JTQu9GPINGN0YLQvtCz0L4g0L3QsNC00L4g0LHRg9C0
|
|
||||||
0LXRgiDRg9C60LDQt9Cw0YLRjCDQsiDRgdC/0LXRhtC40LDQu9GM0L3QvtC5INGA0LXQutC+0LzQ
|
|
||||||
tdC90LTQvtCy0LDQvdC90L7QuSDRhNC+0YDQvNC1INC60L7RgNGA0LXQutGC0L3Ri9C1INGB0LLQ
|
|
||||||
tdC00LXQvdC40Y8g0Lgg0L3QsNC/0YDQsNCy0LjRgtGMINC40YUg0L/QviDQotCa0KEg0YfQtdGA
|
|
||||||
0LXQtyDQvtC/0LXRgNCw0YLQvtGA0LAg0Y3Qu9C10LrRgtGA0L7QvdC90L7Qs9C+INC00L7QutGD
|
|
||||||
0LzQtdC90YLQvtC+0LHQvtGA0L7RgtCwINCyINC90LDQu9C+0LPQvtCy0YvQtSDQvtGA0LPQsNC9
|
|
||||||
0YsuINCf0YDQtdC00YHRgtCw0LLQu9GP0YLRjCDRg9GC0L7Rh9C90LXQvdC90YPRjiDQvdCw0LvQ
|
|
||||||
vtCz0L7QstGD0Y4g0LTQtdC60LvQsNGA0LDRhtC40Y4g0L/QviDQndCU0KEg0LIg0YLQsNC60L7Q
|
|
||||||
vCDRgdC70YPRh9Cw0LUg0L3QtSDRgtGA0LXQsdGD0LXRgtGB0Y8uPGJyPg0KPHA+DQrQldGB0LvQ
|
|
||||||
uCDQttC1INGA0LDRgdGF0L7QttC00LXQvdC40Y8g0LLRi9C30LLQsNC90Ysg0L3QtdCy0LXRgNC9
|
|
||||||
0YvQvCDRgNCw0YHRh9C10YLQvtC8INGB0YPQvNC80Ysg0L3QsNC70L7Qs9CwINC40LvQuCDRgdGH
|
|
||||||
0LXRgi3RhNCw0LrRgtGD0YDQsCDQvtGC0YDQsNC20LXQvSDQvtGI0LjQsdC+0YfQvdC+LCDRgtC+
|
|
||||||
INCyINGB0L7QvtGC0LLQtdGC0YHRgtCy0LjQuCDRgdC+INGB0YLQsNGC0YzQtdC5IDgxINCd0LDQ
|
|
||||||
u9C+0LPQvtCy0L7Qs9C+INC60L7QtNC10LrRgdCwINCg0L7RgdGB0LjQudGB0LrQvtC5INCk0LXQ
|
|
||||||
tNC10YDQsNGG0LjQuCDQvdC10L7QsdGF0L7QtNC40LzQviDQsdGD0LTQtdGCINC/0YDQtdC00YHR
|
|
||||||
gtCw0LLQuNGC0Ywg0YPRgtC+0YfQvdC10L3QvdGD0Y4g0L3QsNC70L7Qs9C+0LLRg9GOINC00LXQ
|
|
||||||
utC70LDRgNCw0YbQuNGOINC/0L4g0J3QlNChLiDQmCDQt9C00LXRgdGMINC/0YDQtdC00YPRgdC8
|
|
||||||
0LDRgtGA0LjQstCw0LXRgtGB0Y8g0L3QvtCy0YvQuSDQv9C+0LTRhdC+0LQuINCi0LXQv9C10YDR
|
|
||||||
jCDQtNC+0L/Rg9GB0LrQsNC10YLRgdGPINC/0YDQtdC00YHRgtCw0LLQu9C10L3QuNC1INGD0YLQ
|
|
||||||
vtGH0L3QtdC90L3QvtC5INC90LDQu9C+0LPQvtCy0L7QuSDQtNC10LrQu9Cw0YDQsNGG0LjQuCAi
|
|
||||||
0L3QsCDQtNC10LvRjNGC0YMiLiDQotC+INC10YHRgtGMINCyINGD0YLQvtGH0L3QtdC90L3QvtC5
|
|
||||||
INC90LDQu9C+0LPQvtCy0L7QuSDQtNC10LrQu9Cw0YDQsNGG0LjQuCDQt9Cw0L/QvtC70L3Rj9GO
|
|
||||||
0YLRgdGPINGC0L7Qu9GM0LrQviDRgtC1INGA0LDQt9C00LXQu9GLLCDQsiDQutC+0YLQvtGA0YvQ
|
|
||||||
tSDQstC90LXRgdC10L3RiyDQuNGB0L/RgNCw0LLQu9C10L3QuNGPLiDQn9C+0LTRgNC+0LHQvdC1
|
|
||||||
0LUg0L/QvtGA0Y/QtNC+0Log0LfQsNC/0L7Qu9C90LXQvdC40Y8g0L3QsNC70L7Qs9C+0LLQvtC5
|
|
||||||
INC00LXQutC70LDRgNCw0YbQuNC4INC40LfQu9C+0LbQtdC9INCyINC/0YDQuNC60LDQt9C1INCk
|
|
||||||
0J3QoSDQoNC+0YHRgdC40Lgg0L7RgiAyOS4xMC4yMDE0IOKEliDQnNCc0JItNy0zLzU1OEAuPGJy
|
|
||||||
Pg0KPHA+DQo8cD4NCtCd0LDQu9C+0LPQvtC/0LvQsNGC0LXQu9GM0YnQuNC60LDQvNC4INC4INC/
|
|
||||||
0LvQsNGC0LXQu9GM0YnQuNC60LDQvNC4INGB0LHQvtGA0L7QsiDQv9GA0LjQt9C90LDRjtGC0YHR
|
|
||||||
jyDQvtGA0LPQsNC90LjQt9Cw0YbQuNC4INC4INGE0LjQt9C40YfQtdGB0LrQuNC1INC70LjRhtCw
|
|
||||||
LCDQvdCwINC60L7RgtC+0YDRi9GFINCyINGB0L7QvtGC0LLQtdGC0YHRgtCy0LjQuCDRgSDQvdCw
|
|
||||||
0YHRgtC+0Y/RidC40Lwg0JrQvtC00LXQutGB0L7QvCDQstC+0LfQu9C+0LbQtdC90LAg0L7QsdGP
|
|
||||||
0LfQsNC90L3QvtGB0YLRjCDRg9C/0LvQsNGH0LjQstCw0YLRjCDRgdC+0L7RgtCy0LXRgtGB0YLQ
|
|
||||||
stC10L3QvdC+INC90LDQu9C+0LPQuCDQuCAo0LjQu9C4KSDRgdCx0L7RgNGLLjxicj4NCtCSINC/
|
|
||||||
0L7RgNGP0LTQutC1LCDQv9GA0LXQtNGD0YHQvNC+0YLRgNC10L3QvdC+0Lwg0L3QsNGB0YLQvtGP
|
|
||||||
0YnQuNC8INCa0L7QtNC10LrRgdC+0LwsINGE0LjQu9C40LDQu9GLINC4INC40L3Ri9C1INC+0LHQ
|
|
||||||
vtGB0L7QsdC70LXQvdC90YvQtSDQv9C+0LTRgNCw0LfQtNC10LvQtdC90LjRjyDRgNC+0YHRgdC4
|
|
||||||
0LnRgdC60LjRhSDQvtGA0LPQsNC90LjQt9Cw0YbQuNC5INC40YHQv9C+0LvQvdGP0Y7RgiDQvtCx
|
|
||||||
0Y/Qt9Cw0L3QvdC+0YHRgtC4INGN0YLQuNGFINC+0YDQs9Cw0L3QuNC30LDRhtC40Lkg0L/QviDR
|
|
||||||
g9C/0LvQsNGC0LUg0L3QsNC70L7Qs9C+0LIg0Lgg0YHQsdC+0YDQvtCyINC/0L4g0LzQtdGB0YLR
|
|
||||||
gyDQvdCw0YXQvtC20LTQtdC90LjRjyDRjdGC0LjRhSDRhNC40LvQuNCw0LvQvtCyINC4INC40L3R
|
|
||||||
i9GFINC+0LHQvtGB0L7QsdC70LXQvdC90YvRhSDQv9C+0LTRgNCw0LfQtNC10LvQtdC90LjQuS48
|
|
||||||
YnI+DQrQkiDRgdC70YPRh9Cw0Y/RhSwg0L/RgNC10LTRg9GB0LzQvtGC0YDQtdC90L3Ri9GFINC9
|
|
||||||
0LDRgdGC0L7Rj9GJ0LjQvCDQmtC+0LTQtdC60YHQvtC8LCDQvdCw0LvQvtCz0L7Qv9C70LDRgtC1
|
|
||||||
0LvRjNGJ0LjQutCw0LzQuCDQv9GA0LjQt9C90LDRjtGC0YHRjyDQuNC90L7RgdGC0YDQsNC90L3R
|
|
||||||
i9C1INGB0YLRgNGD0LrRgtGD0YDRiyDQsdC10Lcg0L7QsdGA0LDQt9C+0LLQsNC90LjRjyDRjtGA
|
|
||||||
0LjQtNC40YfQtdGB0LrQvtCz0L4g0LvQuNGG0LAuPHA+DQo8cD4NCtCh0YLQsNGC0YzRjyAyMC4g
|
|
||||||
0JLQt9Cw0LjQvNC+0LfQsNCy0LjRgdC40LzRi9C1INC70LjRhtCwPHA+DQoxLiDQktC30LDQuNC8
|
|
||||||
0L7Qt9Cw0LLQuNGB0LjQvNGL0LzQuCDQu9C40YbQsNC80Lgg0LTQu9GPINGG0LXQu9C10Lkg0L3Q
|
|
||||||
sNC70L7Qs9C+0L7QsdC70L7QttC10L3QuNGPINC/0YDQuNC30L3QsNGO0YLRgdGPINGE0LjQt9C4
|
|
||||||
0YfQtdGB0LrQuNC1INC70LjRhtCwINC4ICjQuNC70LgpINC+0YDQs9Cw0L3QuNC30LDRhtC40Lgs
|
|
||||||
INC+0YLQvdC+0YjQtdC90LjRjyDQvNC10LbQtNGDINC60L7RgtC+0YDRi9C80Lgg0LzQvtCz0YPR
|
|
||||||
giDQvtC60LDQt9GL0LLQsNGC0Ywg0LLQu9C40Y/QvdC40LUg0L3QsCDRg9GB0LvQvtCy0LjRjyDQ
|
|
||||||
uNC70Lgg0Y3QutC+0L3QvtC80LjRh9C10YHQutC40LUg0YDQtdC30YPQu9GM0YLQsNGC0Ysg0LjR
|
|
||||||
hSDQtNC10Y/RgtC10LvRjNC90L7RgdGC0Lgg0LjQu9C4INC00LXRj9GC0LXQu9GM0L3QvtGB0YLQ
|
|
||||||
uCDQv9GA0LXQtNGB0YLQsNCy0LvRj9C10LzRi9GFINC40LzQuCDQu9C40YYsINCwINC40LzQtdC9
|
|
||||||
0L3Qvjo8YnI+DQoxKSDQvtC00L3QsCDQvtGA0LPQsNC90LjQt9Cw0YbQuNGPINC90LXQv9C+0YHR
|
|
||||||
gNC10LTRgdGC0LLQtdC90L3QviDQuCAo0LjQu9C4KSDQutC+0YHQstC10L3QvdC+INGD0YfQsNGB
|
|
||||||
0YLQstGD0LXRgiDQsiDQtNGA0YPQs9C+0Lkg0L7RgNCz0LDQvdC40LfQsNGG0LjQuCwg0Lgg0YHR
|
|
||||||
g9C80LzQsNGA0L3QsNGPINC00L7Qu9GPINGC0LDQutC+0LPQviDRg9GH0LDRgdGC0LjRjyDRgdC+
|
|
||||||
0YHRgtCw0LLQu9GP0LXRgiDQsdC+0LvQtdC1IDIwINC/0YDQvtGG0LXQvdGC0L7Qsi4g0JTQvtC7
|
|
||||||
0Y8g0LrQvtGB0LLQtdC90L3QvtCz0L4g0YPRh9Cw0YHRgtC40Y8g0L7QtNC90L7QuSDQvtGA0LPQ
|
|
||||||
sNC90LjQt9Cw0YbQuNC4INCyINC00YDRg9Cz0L7QuSDRh9C10YDQtdC3INC/0L7RgdC70LXQtNC+
|
|
||||||
0LLQsNGC0LXQu9GM0L3QvtGB0YLRjCDQuNC90YvRhSDQvtGA0LPQsNC90LjQt9Cw0YbQuNC5INC+
|
|
||||||
0L/RgNC10LTQtdC70Y/QtdGC0YHRjyDQsiDQstC40LTQtSDQv9GA0L7QuNC30LLQtdC00LXQvdC4
|
|
||||||
0Y8g0LTQvtC70LXQuSDQvdC10L/QvtGB0YDQtdC00YHRgtCy0LXQvdC90L7Qs9C+INGD0YfQsNGB
|
|
||||||
0YLQuNGPINC+0YDQs9Cw0L3QuNC30LDRhtC40Lkg0Y3RgtC+0Lkg0L/QvtGB0LvQtdC00L7QstCw
|
|
||||||
0YLQtdC70YzQvdC+0YHRgtC4INC+0LTQvdCwINCyINC00YDRg9Cz0L7QuTs8YnI+DQoyKSDQvtC0
|
|
||||||
0L3QviDRhNC40LfQuNGH0LXRgdC60L7QtSDQu9C40YbQviDQv9C+0LTRh9C40L3Rj9C10YLRgdGP
|
|
||||||
INC00YDRg9Cz0L7QvNGDINGE0LjQt9C40YfQtdGB0LrQvtC80YMg0LvQuNGG0YMg0L/QviDQtNC+
|
|
||||||
0LvQttC90L7RgdGC0L3QvtC80YMg0L/QvtC70L7QttC10L3QuNGOOzxicj4NCjMpINC70LjRhtCw
|
|
||||||
INGB0L7RgdGC0L7Rj9GCINCyINGB0L7QvtGC0LLQtdGC0YHRgtCy0LjQuCDRgSDRgdC10LzQtdC5
|
|
||||||
0L3Ri9C8INC30LDQutC+0L3QvtC00LDRgtC10LvRjNGB0YLQstC+0Lwg0KDQvtGB0YHQuNC50YHQ
|
|
||||||
utC+0Lkg0KTQtdC00LXRgNCw0YbQuNC4INCyINCx0YDQsNGH0L3Ri9GFINC+0YLQvdC+0YjQtdC9
|
|
||||||
0LjRj9GFLCDQvtGC0L3QvtGI0LXQvdC40Y/RhSDRgNC+0LTRgdGC0LLQsCDQuNC70Lgg0YHQstC+
|
|
||||||
0LnRgdGC0LLQsCwg0YPRgdGL0L3QvtCy0LjRgtC10LvRjyDQuCDRg9GB0YvQvdC+0LLQu9C10L3Q
|
|
||||||
vdC+0LPQviwg0LAg0YLQsNC60LbQtSDQv9C+0L/QtdGH0LjRgtC10LvRjyDQuCDQvtC/0LXQutCw
|
|
||||||
0LXQvNC+0LPQvi48cD4NCjIuINCh0YPQtCDQvNC+0LbQtdGCINC/0YDQuNC30L3QsNGC0Ywg0LvQ
|
|
||||||
uNGG0LAg0LLQt9Cw0LjQvNC+0LfQsNCy0LjRgdC40LzRi9C80Lgg0L/QviDQuNC90YvQvCDQvtGB
|
|
||||||
0L3QvtCy0LDQvdC40Y/QvCwg0L3QtSDQv9GA0LXQtNGD0YHQvNC+0YLRgNC10L3QvdGL0Lwg0L/R
|
|
||||||
g9C90LrRgtC+0LwgMSDQvdCw0YHRgtC+0Y/RidC10Lkg0YHRgtCw0YLRjNC4LCDQtdGB0LvQuCDQ
|
|
||||||
vtGC0L3QvtGI0LXQvdC40Y8g0LzQtdC20LTRgyDRjdGC0LjQvNC4INC70LjRhtCw0LzQuCDQvNC+
|
|
||||||
0LPRg9GCINC/0L7QstC70LjRj9GC0Ywg0L3QsCDRgNC10LfRg9C70YzRgtCw0YLRiyDRgdC00LXQ
|
|
||||||
u9C+0Log0L/QviDRgNC10LDQu9C40LfQsNGG0LjQuCDRgtC+0LLQsNGA0L7QsiAo0YDQsNCx0L7R
|
|
||||||
giwg0YPRgdC70YPQsykuPGJyPg0KPGJyPg0KPGJyPg0K0J3QtdC00LDQstC90L4gwqvRhtC40YTR
|
|
||||||
gNC+0LLQsNGPINGN0LrQvtC90L7QvNC40LrQsMK7INC+0LTQtdGA0LbQsNC70LAg0LLQsNC20L3R
|
|
||||||
g9GOINC/0L7QsdC10LTRgywg0LLRi9C60LjQvdGD0LIg0LjQtyDQv9GP0YLQtdGA0LrQuCDRgdCw
|
|
||||||
0LzRi9GFINC00L7RgNC+0LPQuNGFINC60L7QvNC/0LDQvdC40Lkg0LzQuNGA0LAg0L/QvtGB0LvQ
|
|
||||||
tdC00L3QtdCz0L4g0L/RgNC10LTRgdGC0LDQstC40YLQtdC70Y8gwqvRgNC10LDQu9GM0L3QvtCz
|
|
||||||
0L4g0YHQtdC60YLQvtGA0LDCuyBFeHhvbk1vYmlsLiDQndC+INC/0L7QutCwINCx0LjRgtCy0LAg
|
|
||||||
wqvRgdC10YDQstC10YDQsCDRgdC+INGB0YLQsNC90LrQvtC8wrsg0L/RgNC40LLQvtC00LjRgiDQ
|
|
||||||
uiDRgtC+0YDQvNC+0LbQtdC90LjRjiDRjdC60L7QvdC+0LzQuNGH0LXRgdC60L7Qs9C+INGA0L7R
|
|
||||||
gdGC0LAuINCe0YfQtdCy0LjQtNC90L4sINGH0YLQviDQtNC70Y8g0L7QutC+0L3Rh9Cw0YLQtdC7
|
|
||||||
0YzQvdC+0LPQviDRhNC+0YDQvNC40YDQvtCy0LDQvdC40Y8g0L3QvtCy0L7Qs9C+INGN0LrQvtC9
|
|
||||||
0L7QvNC40YfQtdGB0LrQvtCz0L4g0YPQutC70LDQtNCwINC80LjRgNGDINC/0YDQuNC00LXRgtGB
|
|
||||||
0Y8g0L/QtdGA0LXQttC40YLRjCDQtdGJ0LUg0L3QtSDQvtC00L3QviDQv9C+0YLRgNGP0YHQtdC9
|
|
||||||
0LjQtS48cD4NCiA8YnI+DQo8cD4NCjxwPg0KPHA+DQog0J/Rj9GC0LXRgNC60YMg0LrRgNGD0L/Q
|
|
||||||
vdC10LnRiNC40YUg0L/QviDRgNGL0L3QvtGH0L3QvtC5INGB0YLQvtC40LzQvtGB0YLQuCDQutC+
|
|
||||||
0LzQv9Cw0L3QuNC5INC+0LrQutGD0L/QuNGA0L7QstCw0LvQuCBJVC3Qs9C40LPQsNC90YLRizxi
|
|
||||||
cj4NCtCd0LXRhNGC0Y/QvdC40LrQvtCyINGB0LzQtdC90LjQu9C4INGC0LXRhdC90L7Qu9C+0LPQ
|
|
||||||
uNC4PGJyPg0K0J3QsCDRgdC80LXQvdGDINGA0LXRgdGD0YDRgdC90L7QuSDRjdC60L7QvdC+0LzQ
|
|
||||||
uNC60LUg0L/RgNC40YXQvtC00LjRgiDRgtC10YXQvdC+0LvQvtCz0LjRh9C10YHQutCw0Y8uINCV
|
|
||||||
0YHQu9C4INGA0LDQvdGM0YjQtSDQsiDRgtC+0L8tNSDQu9C40LTQtdGA0L7QsiDQv9C+INC60LDQ
|
|
||||||
v9C40YLQsNC70LjQt9Cw0YbQuNC4IElULdCz0LjQs9Cw0L3RgtGLINC/0L7Qv9Cw0LTQsNC70Lgg
|
|
||||||
0LvQuNGI0Ywg0LjQt9GA0LXQtNC60LAsINGC0L4g0YLQtdC/0LXRgNGMINCy0YHRji4uLiDihpI8
|
|
||||||
YnI+DQrQndC+INC90Lgg0LIg0L7QtNC90L7QvCDQsdCw0L3QutC+0LzQsNGC0LUg0YHQvdGP0YLR
|
|
||||||
jCDQtNC10L3QtdCzINC90LUg0L/QvtC70YPRh9C40LvQvtGB0YwuINCi0LXQu9C10YTQvtC9INCx
|
|
||||||
0LDQvdC60LAg0L3QtSDQvtGC0LLQtdGH0LDQuywg0YHQsNC50YIg0LLQuNGB0LXQuy4g0KfQtdGA
|
|
||||||
0LXQtyDQv9C+0LvRh9Cw0YHQsCDQstGL0Y/RgdC90LjQu9C+0YHRjCwg0YfRgtC+INCx0LDQvdC6
|
|
||||||
0L7QstGB0LrQuNC1INGB0LjRgdGC0LXQvNGLINGA0YPRhdC90YPQu9C4LCDQsCDRgdGH0LXRgtCw
|
|
||||||
INC00LXRgdGP0YLQutC+0LIg0LzQuNC70LvQuNC+0L3QvtCyINCy0LrQu9Cw0LTRh9C40LrQvtCy
|
|
||||||
INC+0LHQvdGD0LvQtdC90YsuINCU0L7Qu9C70LDRgCwg0YTRg9C90YIg0Lgg0LXQstGA0L4g0L/R
|
|
||||||
gNC10LLRgNCw0YLQuNC70LjRgdGMINCyINC/0YvQu9GMLCDQt9Cw0YLQviDRgtC1LCDQutGC0L4g
|
|
||||||
0LfQsNC/0LDRgdCw0LvRgdGPINC30L7Qu9C+0YLQvtC8INC4INC/0L7QutGD0L/QsNC7INC60YDQ
|
|
||||||
uNC/0YLQvtCy0LDQu9GO0YLRiywg0L/QvtGC0LjRgNCw0LvQuCDRgNGD0LrQuC4g0KHRgtGA0LDR
|
|
||||||
hSDQuCDRgNCw0YHRgtC10YDRj9C90L3QvtGB0YLRjCDigJQg0LLQvtGCINC00LLQsCDQs9C70LDQ
|
|
||||||
stC90YvRhSDRgdC70L7QstCwLCDQutC+0YLQvtGA0YvQtSDQsdGL0LvQuCDQsiDQt9Cw0LPQvtC7
|
|
||||||
0L7QstC60LDRhSDQvdC+0LLQvtGB0YLQvdGL0YUg0YHQvtC+0LHRidC10L3QuNC5INCy0YHQtdGF
|
|
||||||
INC40L3RhNC+0YDQvNCw0LPQtdC90YLRgdGC0LIuINCd0L7QstGL0Lkg0LTQuNCy0L3Ri9C5INC8
|
|
||||||
0LjRgCDQstGB0YLRgNC10YfQsNC7INGB0LLQvtC40YUg0LjRgdC/0YPQs9Cw0L3QvdGL0YUg0L7Q
|
|
||||||
sdC40YLQsNGC0LXQu9C10LnigKY8cD4NCjxicj4NCtCa0L7QvdC10YfQvdC+LCDQstC10YHRjNC8
|
|
||||||
0LAg0LLQtdGA0L7Rj9GC0L3Qviwg0YfRgtC+INGB0YbQtdC90LDRgNC40LkgwqvRhNC40LvRjNC8
|
|
||||||
0LAt0LrQsNGC0LDRgdGC0YDQvtGE0YvCuyDQsiDQttC40LfQvdC4INC90LjQutC+0LPQtNCwINC9
|
|
||||||
0LUg0LHRg9C00LXRgiDRgNC10LDQu9C40LfQvtCy0LDQvS4g0J3QviDQstC+0YIg0YfRgtC+INC9
|
|
||||||
0LDQtNC+INGH0LXRgtC60L4g0L/QvtC90LjQvNCw0YLRjDog0L3QvtCy0YvQuSDQvNC40YAsINC9
|
|
||||||
0L7QstCw0Y8g0YbQuNGE0YDQvtCy0LDRjyDRgNC10LDQu9GM0L3QvtGB0YLRjCDRg9C20LUg0L/R
|
|
||||||
gNC40LHQuNGA0LDQtdGCINC6INGA0YPQutCw0Lwg0L7QutGA0YPQttCw0Y7RidC40Lkg0LzQuNGA
|
|
||||||
Ljxicj4NCjxwPg0K0J3QtdC00LDQstC90L4g0LLQv9C10YDQstGL0LUg0LIg0LjRgdGC0L7RgNC4
|
|
||||||
0Lgg0L/Rj9GC0LXRgNC60LAg0YHQsNC80YvRhSDQtNC+0YDQvtCz0LjRhSDQutC+0LzQv9Cw0L3Q
|
|
||||||
uNC5INC80LjRgNCwINGB0YLQsNC70LAg0LjRgdC60LvRjtGH0LjRgtC10LvRjNC90L4gwqvRhtC4
|
|
||||||
0YTRgNC+0LLQvtC5wrsuINCf0L7RgdC70LXQtNC90LjQuSDQv9GA0LXQtNGB0YLQsNCy0LjRgtC1
|
|
||||||
0LvRjCDCq9GB0YLQsNGA0L7Qs9C+INC80LjRgNCwwrsg4oCUINC90LXRhNGC0Y/QvdC+0Lkg0LPQ
|
|
||||||
uNCz0LDQvdGCIEV4eG9uTW9iaWwg0LHRi9C7INCy0YvRgtC10YHQvdC10L0g0YEg0L/Rj9GC0L7Q
|
|
||||||
s9C+INC80LXRgdGC0LAg0LjQvdGC0LXRgNC90LXRgi3QvNCw0LPQsNC30LjQvdC+0LwgQW1hem9u
|
|
||||||
INC4INGB0L7RhtGB0LXRgtGM0Y4gRmFjZWJvb2suINCf0LXRgNCy0YvQtSDRgtGA0Lgg0L/QvtC3
|
|
||||||
0LjRhtC40Lgg0L/RgNC40L3QsNC00LvQtdC20LDRgiBBcHBsZSwgQWxwaGFiZXQgKNC80LDRgtC1
|
|
||||||
0YDQuNC90YHQutCw0Y8g0LrQvtC80L/QsNC90LjRjyBHb29nbGUpINC4IE1pY3Jvc29mdC4gwqvQ
|
|
||||||
r9Cx0LvQvtGH0L3Ri9C5wrsg0LvQuNC00LXRgCDRgdGC0L7QuNGCINC+0LrQvtC70L4gJDU3MCDQ
|
|
||||||
vNC70YDQtC48YnI+DQo8cD4NCtCf0YDQtdC40LzRg9GJ0LXRgdGC0LLQviBJVC3RgdC10LrRgtC+
|
|
||||||
0YDQsCDQvdCw0YDQsNGB0YLQsNC10YIg0YEg0LrQvtC90YbQsCDQtNC10LLRj9C90L7RgdGC0YvR
|
|
||||||
hSDQs9C+0LTQvtCyINC/0YDQvtGI0LvQvtCz0L4g0LLQtdC60LAuINCi0L7Qs9C00LAg0L/RgNC+
|
|
||||||
0LjQt9C+0YjQtdC7INGE0LDQu9GM0YHRgtCw0YDRgiwg0LLRi9C70LjQstGI0LjQudGB0Y8g0LIg
|
|
||||||
0LrRgNC40LfQuNGBINC00L7RgtC60L7QvNC+0LIsINGH0YPRgtGMINCx0YvQu9C+INC90LUg0L/Q
|
|
||||||
vtCz0YDRg9C30LjQstGI0LjQuSDQsNC80LXRgNC40LrQsNC90YHQutGD0Y4g0Y3QutC+0L3QvtC8
|
|
||||||
0LjQutGDINCyINGA0LXRhtC10YHRgdC40Y4g0LIg0L3QsNGH0LDQu9C1IDIwMDAt0YUg0LPQvtC0
|
|
||||||
0L7Qsi4g0J3QviDQuCDRgdC10LnRh9Cw0YEg0L3QvtCy0LDRjyDRhtC40YTRgNC+0LLQsNGPINGN
|
|
||||||
0YDQsCDQvdC40LrQsNC6INC90LUg0LLRi9Cy0LXQtNC10YIg0Y3QutC+0L3QvtC80LjQutGDINC9
|
|
||||||
0LAg0YLRgNCw0LXQutGC0L7RgNC40Y4g0YPRgdGC0L7QudGH0LjQstC+0LPQviDRgNC+0YHRgtCw
|
|
||||||
LjxwPg0KPHA+DQrQkdC+0LvQtdC1INGC0L7Qs9C+LCDRgdGA0LXQtNC90LjQtSDRgtC10LzQv9GL
|
|
||||||
INGA0L7RgdGC0LAg0JLQktCfINCh0L7QtdC00LjQvdC10L3QvdGL0YUg0KjRgtCw0YLQvtCyICjQ
|
|
||||||
uNC80LXQvdC90L4g0LIg0Y3RgtC+0Lkg0YHRgtGA0LDQvdC1INCx0LDQt9C40YDRg9C10YLRgdGP
|
|
||||||
INCx0L7Qu9GM0YjQuNC90YHRgtCy0L4g0LrRgNGD0L/QvdC10LnRiNC40YUg0LLRi9GB0L7QutC+
|
|
||||||
0YLQtdGF0L3QvtC70L7Qs9C40YfQvdGL0YUg0LrQvtGA0L/QvtGA0LDRhtC40LkpINCyIFhYSSDQ
|
|
||||||
stC10LrQtSDQvdCw0YXQvtC00Y/RgtGB0Y8g0L3QsCDQvNC40L3QuNC80LDQu9GM0L3QvtC8INGD
|
|
||||||
0YDQvtCy0L3QtSDQv9C+INGB0YDQsNCy0L3QtdC90LjRjiDRgdC+INCy0YLQvtGA0L7QuSDQv9C+
|
|
||||||
0LvQvtCy0LjQvdC+0LkgWFgg0LLQtdC60LAuPHA+DQo8cD4NCtCSIDIwMDHigJMyMDE1INCz0L7Q
|
|
||||||
tNCw0YUg0LDQvNC10YDQuNC60LDQvdGB0LrQsNGPINGN0LrQvtC90L7QvNC40LrQsCDRgNC+0YHQ
|
|
||||||
u9CwINC90LAgMSw4JSDQsiDRgdGA0LXQtNC90LXQvCDQt9CwINCz0L7QtC4g0KDQvtGB0YIg0LIg
|
|
||||||
0L/RgNC10LTRi9C00YPRidC40LUg0LTQstC1INC/0Y/RgtC90LDQtNGG0LDRgtC40LvQtdGC0LrQ
|
|
||||||
uCDRgdC+0YHRgtCw0LLQu9GP0LsgMyw0INC4IDMsMyUsINCwINC00L4g0Y3RgtC+0LPQviDQsdGL
|
|
||||||
0Lsg0LXRidC1INCy0YvRiNC1ICjQt9C00LXRgdGMINC4INC00LDQu9C10LUg4oCUINC00LDQvdC9
|
|
||||||
0YvQtSDQktGB0LXQvNC40YDQvdC+0LPQviDQsdCw0L3QutCwKS48YnI+DQo8cD4NCtCYINGN0YLQ
|
|
||||||
viDQvdC1INCy0YHQtSDQsNC90YLQuNGA0LXQutC+0YDQtNGLLiDQotC10LzQv9GLINGA0L7RgdGC
|
|
||||||
0LAg0LfQsCDQv9C+0YHQu9C10LTQvdC40LUg0L/Rj9GC0L3QsNC00YbQsNGC0Ywg0LvQtdGCINC9
|
|
||||||
0Lgg0YDQsNC30YMg0L3QtSDQv9GA0LXQstGL0YjQsNC70LggNCUgKNC80LDQutGB0LjQvNGD0Lwg
|
|
||||||
4oCUIDMsOCUg4oCUINCx0YvQuyDQv9C+0LrQsNC30LDQvSDQsiAyMDA0INCz0L7QtNGDKSwg0YLQ
|
|
||||||
vtCz0LTQsCDQutCw0Log0YDQsNC90LXQtSDRjdGC0LggNCUg0LHRi9C70Lgg0L7QsdGL0YfQvdGL
|
|
||||||
0Lwg0LTQtdC70L7QvCAo0L/QuNC6INCyIDcsMjYlINCx0YvQuyDQv9C+0LrQsNC30LDQvSDQsiAx
|
|
||||||
OTg0INCz0L7QtNGDKS48cD4NCjxwPg0KPHA+DQrQk9C70YPQsdC40L3QsCDQv9Cw0LTQtdC90LjR
|
|
||||||
jyDRjdC60L7QvdC+0LzQuNC60Lgg0LIgMjAwOSDQs9C+0LTRgyAo4oCTMiw3OCUpINGB0YLQsNC7
|
|
||||||
0LAg0LzQsNC60YHQuNC80LDQu9GM0L3QvtC5INGBIDE5NjEg0LPQvtC00LAuINCf0YDQuCDRjdGC
|
|
||||||
0L7QvCDQvNCw0YHRiNGC0LDQsdGLIMKr0L7RgtGB0LrQvtC60LDCuyDQv9C+0YHQu9C1INGB0L/Q
|
|
||||||
sNC00LAgKCsyLDUzJSDQsiAyMDEwINCz0L7QtNGDKSDQsdGL0LvQuCDQvNC40L3QuNC80LDQu9GM
|
|
||||||
0L3Ri9C80Lgg0LfQsCDQstC10YHRjCDQvdCw0LHQu9GO0LTQsNC10LzRi9C5INC/0LXRgNC40L7Q
|
|
||||||
tC4g0JIg0L7QsdGJ0LXQvCwg0L3QtSDQt9GA0Y8g0L/QvtGB0LvQtdC00L3QuNC5INC60YDQuNC3
|
|
||||||
0LjRgSDQv9C+0LvRg9GH0LjQuyDQsiDQqNGC0LDRgtCw0YUg0L3QsNC30LLQsNC90LjQtSDCq9CS
|
|
||||||
0LXQu9C40LrQsNGPINGA0LXRhtC10YHRgdC40Y/CuyAoR3JlYXQgUmVjZXNzaW9uKS48cD4NCjxi
|
|
||||||
cj4NCiDQm9Cw0LfQtdGA0Ysg0Lgg0LTRgNC+0L3RiyDQvtGCIEZhY2Vib29rINC4INC00YDRg9Cz
|
|
||||||
0LjQtSDRgdC/0L7RgdC+0LHRiyDQvtCx0LXRgdC/0LXRh9C40YLRjCDQsdC10LTQvdGL0LUg0YHR
|
|
||||||
gtGA0LDQvdGLINC40L3RgtC10YDQvdC10YLQvtC8PGJyPg0K0JvQsNC30LXRgNGLINC90LAg0LHQ
|
|
||||||
tdC00L3QvtGB0YLRjDxicj4NCtCW0LXQu9Cw0L3QuNC1INCc0LDRgNC60LAg0KbRg9C60LXRgNCx
|
|
||||||
0LXRgNCz0LAg0L/QvtC60YDRi9GC0Ywg0LjQvdGC0LXRgNC90LXRgtC+0Lwg0LLQtdGB0Ywg0LzQ
|
|
||||||
uNGAINC90LDQsdC40YDQsNC10YIg0L3QvtCy0YvQtSDQvtCx0L7RgNC+0YLRiyDigJQg0Log0LTR
|
|
||||||
gNC+0L3QsNC8LCDQutC+0YLQvtGA0YvQtSDQsdGD0LTRg9GCINC+0LHQtdGB0L/QtdGH0LjQstCw
|
|
||||||
0YLRjCDRgdC10YLRjCDQsiDRgtGA0YPQtNC90L7QtNC+0YHRgtGD0L/QvdGL0YUuLi4g4oaSPHA+
|
|
||||||
DQrQn9GA0LXQt9C40LTQtdC90YLRgdGC0LLQviDQkdCw0YDQsNC60LAg0J7QsdCw0LzRiyDQvNC+
|
|
||||||
0LbQvdC+INCy0L7QvtCx0YnQtSDRgdGH0LjRgtCw0YLRjCDRgdCw0LzRi9C8INC90LXRg9C00LDR
|
|
||||||
h9C90YvQvCDRgSDRgtC+0YfQutC4INC30YDQtdC90LjRjyDRjdC60L7QvdC+0LzQuNGH0LXRgdC6
|
|
||||||
0LjRhSDRg9GB0L/QtdGF0L7Qsi4g0JfQsCDQv9GA0LXQtNGL0LTRg9GJ0LjQtSDRgdC10LzRjCDQ
|
|
||||||
u9C10YIg0JLQktCfINCh0KjQkCDRg9Cy0LXQu9C40YfQuNCy0LDQu9GB0Y8g0YHRgNC10LTQvdC1
|
|
||||||
0LPQvtC00L7QstGL0LzQuCDRgtC10LzQv9Cw0LzQuCAxLDQlLiDQndC4INC/0YDQuCDQvtC00L3Q
|
|
||||||
vtC8INC/0YDQtdC30LjQtNC10L3RgtC1LCDQvdCw0YfQuNC90LDRjyDRgSDQlNC20L7QvdCwINCa
|
|
||||||
0LXQvdC90LXQtNC4LCDRgtCw0Log0LzQtdC00LvQtdC90L3QviDRjdC60L7QvdC+0LzQuNC60LAg
|
|
||||||
0L3QtSDRgNC+0YHQu9CwLjxwPg0KPGJyPg0K0KLQtdC60YPRidC40Lkg0LPQvtC0INC90LUg0L/R
|
|
||||||
gNC40L3QtdGB0LXRgiDQvdC40YfQtdCz0L4g0YXQvtGA0L7RiNC10LPQvi4g0JIg0L/QtdGA0LLQ
|
|
||||||
vtC8INC60LLQsNGA0YLQsNC70LUg0JLQktCfINCy0YvRgNC+0YEg0L3QsCAxLDElINCyINCz0L7Q
|
|
||||||
tNC+0LLQvtC8INC40YHRh9C40YHQu9C10L3QuNC4LCDQstC+INCy0YLQvtGA0L7QvCwg0L/QviDQ
|
|
||||||
v9C10YDQstC+0Lkg0L7RhtC10L3QutC1LCDQvdCwIDEsMiUuINCf0YDQvtCz0L3QvtC3INC90LAg
|
|
||||||
0LLQtdGB0Ywg0LPQvtC0IOKAlCAy4oCTMiwyJSwg0YfRgtC+INCx0YPQtNC10YIg0LzQtdC90YzR
|
|
||||||
iNC1INC/0YDQvtGI0LvQvtCz0L7QtNC90LjRhSAyLDQlLjxwPg0KPHA+DQrQp9C70LXQvSDRgdC+
|
|
||||||
0LLQtdGC0LAg0YPQv9GA0LDQstC70Y/RjtGJ0LjRhSDQpNC10LTQtdGA0LDQu9GM0L3QvtC5INGA
|
|
||||||
0LXQt9C10YDQstC90L7QuSDRgdC40YHRgtC10LzRiyDQodCo0JAg0JTQttC10YDQvtC8INCf0LDR
|
|
||||||
g9GN0LvQuyDQsiDQuNC90YLQtdGA0LLRjNGOIEZpbmFuY2lhbCBUaW1lcyDQt9Cw0Y/QstC40Lss
|
|
||||||
INGH0YLQviDQtdGB0YLRjCDRgNC40YHQuiDQstGC0Y/Qs9C40LLQsNC90LjRjyDQsiDQtNC70LjR
|
|
||||||
gtC10LvRjNC90YvQuSDQv9C10YDQuNC+0LQg0YHQu9Cw0LHQvtCz0L4g0YDQvtGB0YLQsC48YnI+
|
|
||||||
DQo8cD4NCtCd0LDQtNC+INGC0LDQutC20LUg0YPRh9C40YLRi9Cy0LDRgtGMLCDRh9GC0L4g0Y3R
|
|
||||||
gtC+0YIg0LzQuNC90LjQvNCw0LvRjNC90YvQuSDRgNC+0YHRgiDQv9C+0YHQu9C10LTQvdC40YUg
|
|
||||||
0LvQtdGCINGB0L7Qv9GA0L7QstC+0LbQtNCw0LXRgtGB0Y8g0YPQstC10LvQuNGH0LXQvdC40LXQ
|
|
||||||
vCDQtNC+0LvQs9CwLiDQk9C+0YHRg9C00LDRgNGB0YLQstC10L3QvdGL0Lkg0LTQvtC70LMg0LLR
|
|
||||||
i9GA0L7RgSDQt9CwINCy0L7RgdC10LzRjCDQu9C10YIg0L/QvtGH0YLQuCDQsiDQtNCy0LAg0YDQ
|
|
||||||
sNC30LAg0Lgg0YHQtdC50YfQsNGBINC/0YDQtdCy0YvRiNCw0LXRgiAxMDAlINCS0JLQnyDQodCo
|
|
||||||
0JAg0Lgg0YHQvtGB0YLQsNCy0LvRj9C10YIgJDE5LDQg0YLRgNC70L0uINCSINGN0YLQvtC8INCz
|
|
||||||
0L7QtNGDLCDQv9C+INC+0YbQtdC90LrQtSDQsNC80LXRgNC40LrQsNC90YHQutC+0LPQviDQvNC4
|
|
||||||
0L3RhNC40L3QsCwg0L7QvSDQstGL0YDQsNGB0YLQtdGCINC10YnQtSDQvdCwICQzODMg0LzQu9GA
|
|
||||||
0LQuPGJyPg0KPGJyPg0K0J7QtNC90L7QstGA0LXQvNC10L3QvdC+INCk0KDQoSDRg9C00LXRgNC2
|
|
||||||
0LjQstCw0LXRgiDQutC70Y7Rh9C10LLRg9GOINGB0YLQsNCy0LrRgyDQvdCwINC80LjQvdC40LzQ
|
|
||||||
sNC70YzQvdC+0Lwg0YPRgNC+0LLQvdC1INC90LjQttC1IDElINGBINC00LXQutCw0LHRgNGPIDIw
|
|
||||||
MDgg0LPQvtC00LAuINCX0LAg0Y3RgtC+INCy0YDQtdC80Y8g0LHRi9C70L4g0YDQtdCw0LvQuNC3
|
|
||||||
0L7QstCw0L3QviDRgtGA0Lgg0YDQsNGD0L3QtNCwINC/0YDQvtCz0YDQsNC80LzRiyDQutC+0LvQ
|
|
||||||
uNGH0LXRgdGC0LLQtdC90L3QvtCz0L4g0YHQvNGP0LPRh9C10L3QuNGPIChRRSksINC60L7RgtC+
|
|
||||||
0YDQsNGPINC30LDQstC10YDRiNC40LvQsNGB0Ywg0LIg0L7QutGC0Y/QsdGA0LUgMjAxNCDQs9C+
|
|
||||||
0LTQsCAo0LIg0YLRgNC10YLRjNC10Lwg0YDQsNGD0L3QtNC1INCyINC80LXRgdGP0YYg0KTQoNCh
|
|
||||||
INC/0L7QutGD0L/QsNC7INGDINCx0LDQvdC60L7QsiDQs9C+0YHRg9C00LDRgNGB0YLQstC10L3Q
|
|
||||||
vdGL0YUg0Lgg0LjQv9C+0YLQtdGH0L3Ri9GFINC+0LHQu9C40LPQsNGG0LjQuSDQvdCwICQ4NSDQ
|
|
||||||
vNC70YDQtCkuPGJyPg0KPHA+DQrQndC+LCDQv9C+0LTRh9C10YDQutC90LXQvCDQtdGJ0LUg0YDQ
|
|
||||||
sNC3LCDQvdC10YHQvNC+0YLRgNGPINC90LAg0LLRgdC1INGN0YLQuCDRjdC60YHRgtGA0LDQvtGA
|
|
||||||
0LTQuNC90LDRgNC90YvQtSDQvNC10YDRiywg0YLQtdC80L/RiyDRgNC+0YHRgtCwINC/0YDQtdCx
|
|
||||||
0YvQstCw0Y7RgiDQvdCwINC80L3QvtCz0L7Qu9C10YLQvdC40YUg0LzQuNC90LjQvNGD0LzQsNGF
|
|
||||||
LCDQsCDQstGB0Y8g0Y3RgtCwINC40YHRgtC+0YDQuNGPINCy0LXQu9C40LrQvtCz0L4g0YLQvtGA
|
|
||||||
0LzQvtC20LXQvdC40Y8g0L/RgNC+0LjRgdGF0L7QtNC40YIg0L3QsCDRhNC+0L3QtSDRgNC10LfQ
|
|
||||||
utC+0LPQviDRg9GB0LjQu9C10L3QuNGPINGA0L7Qu9C4IElULdGB0LXQutGC0L7RgNCwLCDQsCDR
|
|
||||||
gtCw0LrQttC1INGA0L7RgdGC0LAg0YTQuNC90LDQvdGB0L7QstGL0YUg0YDRi9C90LrQvtCyLjxw
|
|
||||||
Pg0KPHA+DQrQmtC70Y7Rh9C10LLQvtC5INCx0LjRgNC20LXQstC+0Lkg0LjQvdC00LXQutGBIFMm
|
|
||||||
UDUwMCDQvdCw0YXQvtC00LjRgtGB0Y8g0L3QsCDQuNGB0YLQvtGA0LjRh9C10YHQutC40YUg0LzQ
|
|
||||||
sNC60YHQuNC80YPQvNCw0YUuINCQ0L3QsNC70LjRgtC40LrQuCBHb2xkbWFuIFNhY2hzINC/0YDQ
|
|
||||||
tdC00YPQv9GA0LXQttC00LDRjtGCOiDQutC+0L3QtdGGINGA0LDQu9C70Lgg0LHQu9C40LfQvtC6
|
|
||||||
LiDQn9GA0LXQtNGL0LTRg9GJ0LjQtSDQv9C10YDQuNC+0LTRiyDRgNC+0YHRgtCwICjQsiAxOTg0
|
|
||||||
4oCTMTk4NyDQuCAxOTk04oCTMTk5OSDQs9C+0LTQsNGFKSDQt9Cw0LrQsNC90YfQuNCy0LDQu9C4
|
|
||||||
0YHRjCDQvtCx0LLQsNC70L7QvCDQutC+0YLQuNGA0L7QstC+0LouPHA+DQo8cD4NCtCi0L4sINGH
|
|
||||||
0YLQviDQv9GA0L7QuNGB0YXQvtC00LjRgiDQsiDQodCo0JAsINCyINGC0L7QuSDQuNC70Lgg0LjQ
|
|
||||||
vdC+0Lkg0YHRgtC10L/QtdC90Lgg0YXQsNGA0LDQutGC0LXRgNC90L4g0Lgg0LTQu9GPINC00YDR
|
|
||||||
g9Cz0LjRhSDRgNCw0LfQstC40YLRi9GFINGB0YLRgNCw0L06INCy0LDQuyDQtNC+0LvQs9C+0LIs
|
|
||||||
INC90LjQt9C60LjQtSDRgtC10LzQv9GLINGA0L7RgdGC0LAsINC+0YLRgdGD0YLRgdGC0LLQuNC1
|
|
||||||
INC40L3RhNC70Y/RhtC40LgsINC/0L7RgdGC0LXQv9C10L3QvdC+0LUg0YHQvtC60YDQsNGJ0LXQ
|
|
||||||
vdC40LUg0YHRgNC10LTQvdC10LPQviDQutC70LDRgdGB0LAg0Lgg0LrQvtC90YbQtdC90YLRgNCw
|
|
||||||
0YbQuNGPINCx0L7Qs9Cw0YLRgdGC0LLQsCDQsiDRgNGD0LrQsNGFINC+0LPRgNCw0L3QuNGH0LXQ
|
|
||||||
vdC90L7Qs9C+INC60YDRg9Cz0LAg0LvRjtC00LXQuS4g0J/RgNC4INGN0YLQvtC8INC40LfQvNC1
|
|
||||||
0L3QtdC90LjQtSDRgdC40YLRg9Cw0YbQuNC4INGBINC/0L7QvNC+0YnRjNGOINC80LXRgCDRjdC6
|
|
||||||
0L7QvdC+0LzQuNGH0LXRgdC60L7QuSDQuCDQtNC10L3QtdC20L3Qvi3QutGA0LXQtNC40YLQvdC+
|
|
||||||
0Lkg0L/QvtC70LjRgtC40LrQuCDQvdC10LLQvtC30LzQvtC20L3Qvi4g0K3RgtC+INC90LUg0YHR
|
|
||||||
h9C40YLQsNGPIMKr0YfQtdGA0L3Ri9GFINC70LXQsdC10LTQtdC5wrsg0LLRgNC+0LTQtSBCcmV4
|
|
||||||
aXQsINC60L7RgtC+0YDRi9C1INGB0LXRjtGCINGB0LzRj9GC0LXQvdC40LUg0Lgg0YXQsNC+0YEg
|
|
||||||
0L3QsCDRgNGL0L3QutCw0YUuPHA+DQo8YnI+DQrQlNC+0LvQs9C+0LUg0LLRgNC10LzRjyDQvNC4
|
|
||||||
0YAg0LLRi9C/0LvRi9Cy0LDQuyDQt9CwINGB0YfQtdGCINCx0YPRgNC90L7Qs9C+INGA0L7RgdGC
|
|
||||||
0LAg0LIg0YHRgtGA0LDQvdCw0YUg0YLRgNC10YLRjNC10LPQviDQvNC40YDQsCwg0LrQvtGC0L7R
|
|
||||||
gNGL0LUg0Y3QutGB0L/QvtGA0YLQuNGA0L7QstCw0LvQuCDRgdGL0YDRjNC1INC/0L4g0LLRi9GB
|
|
||||||
0L7QutC40Lwg0YbQtdC90LDQvCwg0L/QvtC60YPQv9Cw0LvQuCDRgtC+0LLQsNGA0Ysg0Lgg0YLQ
|
|
||||||
tdGF0L3QvtC70L7Qs9C40Lgg0Lgg0YDQsNC30YDQtdGI0LDQu9C4INC+0YLQutGA0YvQstCw0YLR
|
|
||||||
jCDRgyDRgdC10LHRjyDQv9GA0L7QuNC30LLQvtC00YHRgtCy0LAg0YLRgNCw0L3RgdC90LDRhtC4
|
|
||||||
0L7QvdCw0LvRjNC90YvQvCDQutC+0YDQv9C+0YDQsNGG0LjRj9C8LCDRgdC90LDQsdC20LDRjyDQ
|
|
||||||
uNGFINC00LXRiNC10LLQvtC5INGA0LDQsdC+0YfQtdC5INGB0LjQu9C+0LkuINCd0L4g0YHQtdCz
|
|
||||||
0L7QtNC90Y8g0L7QvdC4INGC0LDQutC20LUg0LIg0LrRgNC40LfQuNGB0LUuPGJyPg0KPGJyPg0K
|
|
||||||
INCV0LvQuNC30LDQstC10YLQsCDQkNC70LXQutGB0LDQvdC00YDQvtCy0LAt0JfQvtGA0LjQvdCw
|
|
||||||
INC+INGC0L7QvCwg0LPQvtGC0L7QstC+INC70Lgg0YfQtdC70L7QstC10YfQtdGB0YLQstC+INC6
|
|
||||||
INCz0LXQvdC10YLQuNGH0LXRgdC60L7QuSDQuCDRgtC10YXQvdC+0LvQvtCz0LjRh9C10YHQutC+
|
|
||||||
0LkgwqvRgNC10LTQsNC60YLRg9GA0LXCuzxicj4NCtCn0LXQu9C+0LLQtdC6INC90L7QstGL0Lks
|
|
||||||
INGD0LvRg9GH0YjQtdC90L3Ri9C5PHA+DQrQniDQvdC+0LLQvtC8INGH0LXQu9C+0LLQtdC60LUs
|
|
||||||
IGwnaG9tbWUgbm91dmVhdSwg0LzQtdGH0YLQsNC70Lgg0YHQtdCy0LXRgNC+0LDQvNC10YDQuNC6
|
|
||||||
0LDQvdGB0LrQuNC1INC/0L7RgdC10LvQtdC90YbRiywg0L3QsNGG0LjRgdGC0Ysg0Lgg0LrQvtC8
|
|
||||||
0LzRg9C90LjRgdGC0YssINCi0YDQvtGG0LrQuNC5LCDQp9C1INCT0LXQstCw0YDQsCDQuCDQvNC9
|
|
||||||
0L7Qs9C40LUg0LTRgNGD0LPQuNC1LiDQoNC10YfRjCDRiNC70LAg0L7QsS4uLiDihpI8YnI+DQrQ
|
|
||||||
otCw0Log0L/QvtGH0LXQvNGDINC20LUgwqvRhtC40YTRgNCwwrsg0YLQsNC6INC4INC90LUg0YHR
|
|
||||||
gtCw0LvQsCDRgtC10Lwg0YHQsNC80YvQvCDQtNGA0LDQudCy0LXRgNC+0LwsINC60L7RgtC+0YDR
|
|
||||||
i9C5INCy0LXRgNC90YPQuyDQsdGLINGN0LrQvtC90L7QvNC40LrRgyDQvdCwINGC0YDQsNC10LrR
|
|
||||||
gtC+0YDQuNGOINCy0YvRgdC+0LrQvtCz0L4g0Lgg0YPRgdGC0L7QudGH0LjQstC+0LPQviDRgNC+
|
|
||||||
0YHRgtCwPyDQrdGC0L7QvNGDINC80LXRiNCw0Y7RgiDRgdGC0LDRgNGL0LUg0LjQvdGB0YLQuNGC
|
|
||||||
0YPRgtGLINC4INC40L3RhNGA0LDRgdGC0YDRg9C60YLRg9GA0LAsINGF0L7RgNC+0YjQviDQv9GA
|
|
||||||
0LjRgdC/0L7RgdC+0LHQu9C10L3QvdGL0LUg0LTQu9GPINGC0YDQsNC00LjRhtC40L7QvdC90L7Q
|
|
||||||
uSDRjdC60L7QvdC+0LzQuNC60Lgg0YEg0LXQtSDQs9C70LDQstC10L3RgdGC0LLRg9GO0YnQtdC5
|
|
||||||
INGA0L7Qu9GM0Y4gwqvRgNC10LDQu9GM0L3QvtCz0L4g0YHQtdC60YLQvtGA0LDCuyDQuCDQsdCw
|
|
||||||
0L3QutC+0LLRgdC60L7Qs9C+INC60YDQtdC00LjRgtCwINC60LDQuiDQtNGA0LDQudCy0LXRgNCw
|
|
||||||
INC40L3QstC10YHRgtC40YbQuNC5INC4INC/0L7RgtGA0LXQsdC70LXQvdC40Y8uPHA+DQo8cD4N
|
|
||||||
CtCd0LAg0YHQvNC10L3RgyDRgdGC0LDRgNGL0Lwg0LLQsNC70Y7RgtCw0LwsINCx0LDQvdC60LDQ
|
|
||||||
vCDQuCDQsdC40YDQttCw0LwsINGD0LPQu9C10LLQvtC00L7RgNC+0LTQvdC+0Lkg0Y3QvdC10YDQ
|
|
||||||
s9C10YLQuNC60LUg0Lgg0YLRgNCw0LTQuNGG0LjQvtC90L3Ri9C8INGE0LDQsdGA0LjQutCw0Lwg
|
|
||||||
0YPQttC1INC40LTRg9GCINC90L7QstGL0LUg0YLQtdGF0L3QvtC70L7Qs9C40LguINCd0LDQv9GA
|
|
||||||
0LjQvNC10YAsINCx0LvQvtC60YfQtdC50L0g0Lgg0LHQuNGC0LrQvtC40L3Riywg0LrQvtGC0L7R
|
|
||||||
gNGL0LUg0L/QvtC30LLQvtC70Y/RjtGCINCy0L7QvtCx0YnQtSDQstGL0LLQtdGB0YLQuCDQs9C+
|
|
||||||
0YHRg9C00LDRgNGB0YLQstC+ICjQutCw0Log0Y3QvNC40YLQtdC90YLQsCDQstCw0LvRjtGC0Ysp
|
|
||||||
INC4INCx0LDQvdC60LggKNC60LDQuiDQuNGB0YLQvtGH0L3QuNC6INC60YDQtdC00LjRgtCwINC4
|
|
||||||
INGE0LjQvdCw0L3RgdC+0LLRi9C1INC/0L7RgdGA0LXQtNC90LjQutC4KSDQuNC3INC40LPRgNGL
|
|
||||||
LjxwPg0KPGJyPg0K0J/RgNC10LfQuNC00LXQvdGCINCh0LHQtdGA0LHQsNC90LrQsCDQk9C10YDQ
|
|
||||||
vNCw0L0g0JPRgNC10YQg0L3QtdC+0LTQvdC+0LrRgNCw0YLQvdC+INC/0L7QtNGH0LXRgNC60LjQ
|
|
||||||
stCw0LssINGH0YLQviDRg9C20LUg0LIg0LHQu9C40LbQsNC50YjQtdC1INCy0YDQtdC80Y8g0YLR
|
|
||||||
gNCw0LTQuNGG0LjQvtC90L3Ri9C8INGE0LjQvdCw0L3RgdC+0LLRi9C8INC+0YDQs9Cw0L3QuNC3
|
|
||||||
0LDRhtC40Y/QvCDQv9GA0LjQtNC10YLRgdGPINC60L7QvdC60YPRgNC40YDQvtCy0LDRgtGMINGB
|
|
||||||
INC40L3RgtC10YDQvdC10YIt0LjQvdC00YPRgdGC0YDQuNC10LkuPHA+DQo8cD4NCtCSINC40L3R
|
|
||||||
gtC10YDQstGM0Y4g0LbRg9GA0L3QsNC70YMgSGFydmFyZCBCdXNpbmVzcyBSZXZpZXcg0L7QvSDQ
|
|
||||||
vtGC0LzQtdGH0LDQuywg0YfRgtC+INCx0LDQvdC6IMKr0LDQsdGB0L7Qu9GO0YLQvdC+INC90LXQ
|
|
||||||
utC+0L3QutGD0YDQtdC90YLQvtGB0L/QvtGB0L7QsdC10L3CuyDQsiDQv9C10YDRgdC/0LXQutGC
|
|
||||||
0LjQstC1INC00LXRgdGP0YLQuCDQu9C10YIsINC/0L7RgdC60L7Qu9GM0LrRgyDQv9GA0L7QuNCz
|
|
||||||
0YDRi9Cy0LDQtdGCINGC0LXRhdC90L7Qu9C+0LPQuNGH0LXRgdC60LjQvCDQutC+0LzQv9Cw0L3Q
|
|
||||||
uNGP0LwgKEdvb2dsZSwgQWxpYmFiYSDQuCBBbWF6b24g0Lgg0L/RgC4pLCDQstGL0YXQvtC00Y/R
|
|
||||||
idC40Lwg0L3QsCDRgNGL0L3QvtC6INC/0YDQtdC00L7RgdGC0LDQstC70LXQvdC40Y8g0YTQuNC9
|
|
||||||
0LDQvdGB0L7QstGL0YUg0YPRgdC70YPQsy4gwqvQntC90Lgg0L3QsNC80L3QvtCz0L4g0YHQuNC7
|
|
||||||
0YzQvdC10LUg0L/QvtGH0YLQuCDQstC+INCy0YHQtdC8wrssIOKAlCDQv9C+0LTRh9C10YDQutC4
|
|
||||||
0LLQsNC7INCT0YDQtdGELjxicj4NCjxicj4NCsKr0JXRgdC70Lgg0YMg0L3QsNGBIHRpbWUgdG8g
|
|
||||||
bWFya2V0ICjQstGA0LXQvNGPINCy0YvRhdC+0LTQsCDQv9GA0L7QtNGD0LrRgtCwINC90LAg0YDR
|
|
||||||
i9C90L7QuiDRgSDQvNC+0LzQtdC90YLQsCDRgdC+0LfQtNCw0L3QuNGPLiDigJQgwqvQk9Cw0LfQ
|
|
||||||
tdGC0LAuUnXCuykg0LzQtdGA0Y/QtdGC0YHRjyDQvNC90L7Qs9C40LzQuCDQvNC10YHRj9GG0LDQ
|
|
||||||
vNC4LCDQsCDRgyDQvdC40YUg0YfQsNGB0LDQvNC4LCDRgtC+INC60LDQuiDQvdCw0Lwg0LrQvtC9
|
|
||||||
0LrRg9GA0LjRgNC+0LLQsNGC0Yw/INCd0LjQutCw0LouINCe0L3QuCDQstGB0LXQs9C00LAg0L3Q
|
|
||||||
sNGBINCx0YPQtNGD0YIg0L7Qv9C10YDQtdC20LDRgtGMLiDQp9GC0L4g0L3QsNC8INC90YPQttC9
|
|
||||||
0L4g0YHQtNC10LvQsNGC0Yw/INCi0LDQutC+0Lkg0LbQtSB0aW1lIHRvIG1hcmtldMK7LCDigJQg
|
|
||||||
0LfQsNGP0LLQuNC7INC/0YDQtdC30LjQtNC10L3RgiDQodCx0LXRgNCx0LDQvdC60LAuPHA+DQo8
|
|
||||||
cD4NCtCR0LXQt9GD0YHQu9C+0LLQvdC+LCDQuCDQsdC70L7QutGH0LXQudC9LCDQuCDQsdC40YLQ
|
|
||||||
utC+0LjQvdGLINC10YnQtSDQvdGD0LbQtNCw0Y7RgtGB0Y8g0LIg0LTQvtGA0LDQsdC+0YLQutC1
|
|
||||||
LiDQndC10LTQsNCy0L3Rj9GPINGF0LDQutC10YDRgdC60LDRjyDQsNGC0LDQutCwINC90LAg0L7Q
|
|
||||||
tNC90YMg0LjQtyDQutGA0YPQv9C90LXQudGI0LjRhSDQsdC40YLQutC+0LjQvS3QsdC40YDQtiDQ
|
|
||||||
siDQk9C+0L3QutC+0L3Qs9C1INC/0YDQuNCy0LXQu9CwINC6INC/0LDQtNC10L3QuNGOINC60YPR
|
|
||||||
gNGB0LAg0LrRgNC40L/RgtC+0LLQsNC70Y7RgtGLINC90LAgMjAlLiDQpdCw0LrQtdGA0LDQvCDR
|
|
||||||
g9C00LDQu9C+0YHRjCDQv9C+0YXQuNGC0LjRgtGMIDEyMCDRgtGL0YEuINCx0LjRgtC60L7QuNC9
|
|
||||||
0L7Qsiwg0LrQvtGC0L7RgNGL0LUg0L3QsCDRgtC+0YIg0LzQvtC80LXQvdGCINCx0YvQu9C4INGN
|
|
||||||
0LrQstC40LLQsNC70LXQvdGC0L3RiyDQv9GA0LjQvNC10YDQvdC+ICQ3MCDQvNC70L0uPGJyPg0K
|
|
||||||
PGJyPg0K0JLQv9GA0L7Rh9C10LwsINC/0L7QsdC10LTQvdGD0Y4g0L/QvtGB0YLRg9C/0Ywg0LHQ
|
|
||||||
u9C+0LrRh9C10LnQvdCwINCy0YDRj9C0INC70Lgg0YPQtNCw0YHRgtGB0Y8g0L7RgdGC0LDQvdC+
|
|
||||||
0LLQuNGC0YwuINCQINCy0LXQtNGMINC10YHRgtGMINC10YnQtSDRgtC10YXQvdC+0LvQvtCz0LjQ
|
|
||||||
uCDQuNC3INGA0LXQsNC70YzQvdC+0LPQviDRgdC10LrRgtC+0YDQsCDigJQg0LDQu9GM0YLQtdGA
|
|
||||||
0L3QsNGC0LjQstC90LDRjyDRjdC90LXRgNCz0LXRgtC40LrQsCDQuCDRjdC70LXQutGC0YDQvtC8
|
|
||||||
0L7QsdC40LvQuCwg0LAg0YLQsNC60LbQtSAzRC3Qv9C10YfQsNGC0YwsINGA0L7QsdC+0YLQuNC3
|
|
||||||
0LDRhtC40Y8g0L/RgNC+0LjQt9Cy0L7QtNGB0YLQstC10L3QvdGL0YUg0L/RgNC+0YbQtdGB0YHQ
|
|
||||||
vtCyLCDRgdC40L3RgtC10Lcg0L/QuNGJ0LXQstGL0YUg0L/RgNC+0LTRg9C60YLQvtCyINC4INC8
|
|
||||||
0L3QvtCz0L7QtSDQtNGA0YPQs9C+0LUuPHA+DQo8YnI+DQrQktC/0L7Qu9C90LUg0LLQtdGA0L7R
|
|
||||||
j9GC0L3Qviwg0YfRgtC+INC90LAg0LPQvtGA0LjQt9C+0L3RgtC1INCx0LvQuNC20LDQudGI0LjR
|
|
||||||
hSDQtNCy0YPRhS3RgtGA0LXRhSDQtNC10YHRj9GC0LjQu9C10YLQuNC5INCx0LDQt9C+0LLRi9C1
|
|
||||||
INC/0L7RgtGA0LXQsdC90L7RgdGC0Lgg0YfQtdC70L7QstC10LrQsCDQsiDQv9C40YnQtSwg0L7Q
|
|
||||||
tNC10LbQtNC1INC4INC/0LXRgNC10LTQstC40LbQtdC90LjQuCDQsdGD0LTRg9GCINGD0LTQvtCy
|
|
||||||
0LvQtdGC0LLQvtGA0Y/RgtGM0YHRjyDQv9C+0YfRgtC4INCx0LXRgdC/0LvQsNGC0L3Qvi4g0J/Q
|
|
||||||
u9Cw0YLQuNGC0Ywg0L/RgNC40LTQtdGC0YHRjyDQsiDQvtGB0L3QvtCy0L3QvtC8INC30LAgwqvQ
|
|
||||||
utC+0L3RgtC10L3RgsK7Ljxicj4NCjxicj4NCtCR0LXQtNC90L7RgdGC0Ywg0LIg0YHRgtGA0LDQ
|
|
||||||
vdCw0YUgwqvQt9C+0LvQvtGC0L7Qs9C+INC80LjQu9C70LjQsNGA0LTQsMK7INC+0LrQvtC90YfQ
|
|
||||||
sNGC0LXQu9GM0L3QviDRg9C50LTQtdGCINCyINC/0YDQvtGI0LvQvtC1LCDQs9GA0LDQttC00LDQ
|
|
||||||
vdC1INCx0YPQtNGD0YIg0L/QvtC70YPRh9Cw0YLRjCDQs9Cw0YDQsNC90YLQuNGA0L7QstCw0L3Q
|
|
||||||
vdGL0Lkg0LTQvtGF0L7QtCDQv9GA0Y/QvNC+INGBINGA0L7QttC00LXQvdC40Y8gKNC/0YDQuNCy
|
|
||||||
0YvRh9C90YvRhSDQsdGD0LzQsNC20L3Ri9GFINC00LXQvdC10LMg0L3QtSDQsdGD0LTQtdGCLCDQ
|
|
||||||
uNGFINGBINCx0L7Qu9GM0YjQvtC5INC00L7Qu9C10Lkg0LLQtdGA0L7Rj9GC0L3QvtGB0YLQuCDQ
|
|
||||||
vtGC0LzQtdC90Y/RgiDRg9C20LUg0LTQvtCy0L7Qu9GM0L3QviDRgdC60L7RgNC+KS4g0JIg0YLQ
|
|
||||||
viDQttC1INCy0YDQtdC80Y8g0LLRi9Cx0LjRgtGM0YHRjyDQsiDRgdGA0LXQtNC90LjQuSDQutC7
|
|
||||||
0LDRgdGBINC40LvQuCDRgNCw0LfQsdC+0LPQsNGC0LXRgtGMINGB0YLQsNC90LXRgiDQs9C+0YDQ
|
|
||||||
sNC30LTQviDRgdC70L7QttC90LXQtSwg0YfQtdC8INGB0LXQudGH0LDRgS48YnI+DQo8YnI+DQrQ
|
|
||||||
ndC+INCy0L7RgiDRh9GC0L4g0YHRgtC+0LjRgiDQv9C+0LTRh9C10YDQutC90YPRgtGMLiDQnNC4
|
|
||||||
0YAg0LbQtNC10YIg0L3QvtCy0LDRjyDRjdC60L7QvdC+0LzQuNGH0LXRgdC60LDRjyDRgNC10LLQ
|
|
||||||
vtC70Y7RhtC40Y8sINC60L7RgtC+0YDQsNGPINCx0YPQtNC10YIg0YHQvtC/0YDQvtCy0L7QttC0
|
|
||||||
0LDRgtGM0YHRjyDQutGA0LjQt9C40YHQvdGL0LzQuCDQv9C10YDQuNC+0LTQsNC80LguINCi0LXQ
|
|
||||||
vCDQsdC+0LvQtdC1INGH0YLQviwg0LrQsNC6INCx0YvQu9C+INC/0L7QutCw0LfQsNC90L4g0LLR
|
|
||||||
i9GI0LUsINC/0YDQtdC00L/QvtGB0YvQu9C+0Log0LTQu9GPINC90L7QstC+0LPQviDQvtCx0LLQ
|
|
||||||
sNC70LAg0LHQvtC70LXQtSDRh9C10Lwg0LTQvtGB0YLQsNGC0L7Rh9C90L4uINCS0L7Qv9GA0L7R
|
|
||||||
gSDRgtC+0LvRjNC60L4g0LIg0YLQvtC8LCDQs9C00LUg0Lgg0LrQvtCz0LTQsCDQvdCw0YfQvdC1
|
|
||||||
0YLRgdGPINC+0LHRgNGD0YjQtdC90LjQtS48YnI+DQo8cD4NCjxicj4NCg==
|
|
||||||
--2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7
|
|
||||||
Content-Type: application/octet-stream; name="ФНС_РФ558.zip"
|
|
||||||
Content-Disposition:attachment; filename="ФНС_РФ558.zip"
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
|
|
||||||
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
|
|
||||||
--2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7
|
|
||||||
Binary file not shown.
|
|
@ -4590,7 +4590,7 @@
|
||||||
"org_id": "2",
|
"org_id": "2",
|
||||||
"orgc_id": "2",
|
"orgc_id": "2",
|
||||||
"proposal_email_lock": false,
|
"proposal_email_lock": false,
|
||||||
"publish_timestamp": 0,
|
"publish_timestamp": "0",
|
||||||
"published": false,
|
"published": false,
|
||||||
"sharing_group_id": "0",
|
"sharing_group_id": "0",
|
||||||
"threat_level_id": "3",
|
"threat_level_id": "3",
|
||||||
|
|
|
||||||
|
|
@ -4593,7 +4593,7 @@
|
||||||
"org_id": "2",
|
"org_id": "2",
|
||||||
"orgc_id": "2",
|
"orgc_id": "2",
|
||||||
"proposal_email_lock": false,
|
"proposal_email_lock": false,
|
||||||
"publish_timestamp": 0,
|
"publish_timestamp": "0",
|
||||||
"published": false,
|
"published": false,
|
||||||
"sharing_group_id": "0",
|
"sharing_group_id": "0",
|
||||||
"threat_level_id": "3",
|
"threat_level_id": "3",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"distribution": "0",
|
"distribution": "0",
|
||||||
"proposal_email_lock": false,
|
"proposal_email_lock": false,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"publish_timestamp": 0,
|
"publish_timestamp": "0",
|
||||||
"sharing_group_id": "0",
|
"sharing_group_id": "0",
|
||||||
"disable_correlation": false,
|
"disable_correlation": false,
|
||||||
"event_creator_email": "raphael.vinot@circl.lu",
|
"event_creator_email": "raphael.vinot@circl.lu",
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@
|
||||||
"org_id": "1",
|
"org_id": "1",
|
||||||
"orgc_id": "1",
|
"orgc_id": "1",
|
||||||
"proposal_email_lock": true,
|
"proposal_email_lock": true,
|
||||||
"publish_timestamp": 0,
|
"publish_timestamp": "0",
|
||||||
"published": false,
|
"published": false,
|
||||||
"sharing_group_id": "0",
|
"sharing_group_id": "0",
|
||||||
"threat_level_id": "1",
|
"threat_level_id": "1",
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,33 @@
|
||||||
from email.message import EmailMessage
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# import json
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from email.message import EmailMessage
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import List
|
|
||||||
from pymisp.tools import EMailObject
|
|
||||||
from pathlib import Path
|
|
||||||
from os import urandom
|
from os import urandom
|
||||||
import json
|
from pathlib import Path
|
||||||
|
from typing import TypeVar, Type
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from pymisp.tools import EMailObject
|
||||||
from pymisp.exceptions import PyMISPNotImplementedYet, InvalidMISPObject
|
from pymisp.exceptions import PyMISPNotImplementedYet, InvalidMISPObject
|
||||||
|
|
||||||
|
T = TypeVar('T', bound='TestEmailObject')
|
||||||
|
|
||||||
|
|
||||||
class TestEmailObject(unittest.TestCase):
|
class TestEmailObject(unittest.TestCase):
|
||||||
def test_mail_1(self):
|
|
||||||
email_object = EMailObject(Path("tests/email_testfiles/mail_1.eml"))
|
eml_1: BytesIO
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls: type[T]) -> None:
|
||||||
|
with ZipFile(Path("tests/email_testfiles/mail_1.eml.zip"), 'r') as myzip:
|
||||||
|
with myzip.open('mail_1.eml', pwd=b'AVs are dumb') as myfile:
|
||||||
|
cls.eml_1 = BytesIO(myfile.read())
|
||||||
|
|
||||||
|
def test_mail_1(self) -> None:
|
||||||
|
email_object = EMailObject(pseudofile=self.eml_1)
|
||||||
self.assertEqual(self._get_values(email_object, "subject")[0], "письмо уведом-е")
|
self.assertEqual(self._get_values(email_object, "subject")[0], "письмо уведом-е")
|
||||||
self.assertEqual(self._get_values(email_object, "to")[0], "kinney@noth.com")
|
self.assertEqual(self._get_values(email_object, "to")[0], "kinney@noth.com")
|
||||||
self.assertEqual(self._get_values(email_object, "from")[0], "suvorov.s@nalg.ru")
|
self.assertEqual(self._get_values(email_object, "from")[0], "suvorov.s@nalg.ru")
|
||||||
|
|
@ -27,7 +43,7 @@ class TestEmailObject(unittest.TestCase):
|
||||||
self.assertIsInstance(file_name, str)
|
self.assertIsInstance(file_name, str)
|
||||||
self.assertIsInstance(file_content, BytesIO)
|
self.assertIsInstance(file_content, BytesIO)
|
||||||
|
|
||||||
def test_mail_1_headers_only(self):
|
def test_mail_1_headers_only(self) -> None:
|
||||||
email_object = EMailObject(Path("tests/email_testfiles/mail_1_headers_only.eml"))
|
email_object = EMailObject(Path("tests/email_testfiles/mail_1_headers_only.eml"))
|
||||||
self.assertEqual(self._get_values(email_object, "subject")[0], "письмо уведом-е")
|
self.assertEqual(self._get_values(email_object, "subject")[0], "письмо уведом-е")
|
||||||
self.assertEqual(self._get_values(email_object, "to")[0], "kinney@noth.com")
|
self.assertEqual(self._get_values(email_object, "to")[0], "kinney@noth.com")
|
||||||
|
|
@ -38,7 +54,7 @@ class TestEmailObject(unittest.TestCase):
|
||||||
self.assertIsInstance(email_object.email, EmailMessage)
|
self.assertIsInstance(email_object.email, EmailMessage)
|
||||||
self.assertEqual(len(email_object.attachments), 0)
|
self.assertEqual(len(email_object.attachments), 0)
|
||||||
|
|
||||||
def test_mail_multiple_to(self):
|
def test_mail_multiple_to(self) -> None:
|
||||||
email_object = EMailObject(Path("tests/email_testfiles/mail_multiple_to.eml"))
|
email_object = EMailObject(Path("tests/email_testfiles/mail_multiple_to.eml"))
|
||||||
|
|
||||||
to = self._get_values(email_object, "to")
|
to = self._get_values(email_object, "to")
|
||||||
|
|
@ -48,9 +64,9 @@ class TestEmailObject(unittest.TestCase):
|
||||||
self.assertEqual(to[1], "jan.marek@example.com")
|
self.assertEqual(to[1], "jan.marek@example.com")
|
||||||
self.assertEqual(to_display_name[1], "Marek, Jan")
|
self.assertEqual(to_display_name[1], "Marek, Jan")
|
||||||
|
|
||||||
def test_msg(self):
|
def test_msg(self) -> None:
|
||||||
# Test result of eml converted to msg is the same
|
# Test result of eml converted to msg is the same
|
||||||
eml_email_object = EMailObject(Path("tests/email_testfiles/mail_1.eml"))
|
eml_email_object = EMailObject(pseudofile=self.eml_1)
|
||||||
email_object = EMailObject(Path("tests/email_testfiles/mail_1.msg"))
|
email_object = EMailObject(Path("tests/email_testfiles/mail_1.msg"))
|
||||||
|
|
||||||
self.assertIsInstance(email_object.email, EmailMessage)
|
self.assertIsInstance(email_object.email, EmailMessage)
|
||||||
|
|
@ -64,20 +80,17 @@ class TestEmailObject(unittest.TestCase):
|
||||||
self._get_values(eml_email_object, "to")[0])
|
self._get_values(eml_email_object, "to")[0])
|
||||||
self.assertEqual(self._get_values(email_object, "from")[0],
|
self.assertEqual(self._get_values(email_object, "from")[0],
|
||||||
self._get_values(eml_email_object, "from")[0])
|
self._get_values(eml_email_object, "from")[0])
|
||||||
dirty_display_name = self._get_values(email_object, "from-display-name")[0]
|
self.assertEqual(self._get_values(email_object, "from-display-name")[0],
|
||||||
dirty_display_name = dirty_display_name[:-2] + dirty_display_name[-1]
|
|
||||||
self.assertEqual(dirty_display_name,
|
|
||||||
self._get_values(eml_email_object, "from-display-name")[0])
|
self._get_values(eml_email_object, "from-display-name")[0])
|
||||||
self.assertEqual(len(self._get_values(email_object, "email-body")), 2)
|
self.assertEqual(len(self._get_values(email_object, "email-body")), 2)
|
||||||
|
|
||||||
self.assertEqual(self._get_values(email_object, "received-header-ip"),
|
self.assertEqual(self._get_values(email_object, "received-header-ip"),
|
||||||
self._get_values(eml_email_object, "received-header-ip"))
|
self._get_values(eml_email_object, "received-header-ip"))
|
||||||
|
|
||||||
|
def test_bom_encoded(self) -> None:
|
||||||
def test_bom_encoded(self):
|
|
||||||
"""Test utf-8-sig encoded email"""
|
"""Test utf-8-sig encoded email"""
|
||||||
bom_email_object = EMailObject(Path("tests/email_testfiles/mail_1_bom.eml"))
|
bom_email_object = EMailObject(Path("tests/email_testfiles/mail_1_bom.eml"))
|
||||||
eml_email_object = EMailObject(Path("tests/email_testfiles/mail_1.eml"))
|
eml_email_object = EMailObject(pseudofile=self.eml_1)
|
||||||
|
|
||||||
self.assertIsInstance(bom_email_object.email, EmailMessage)
|
self.assertIsInstance(bom_email_object.email, EmailMessage)
|
||||||
for file_name, file_content in bom_email_object.attachments:
|
for file_name, file_content in bom_email_object.attachments:
|
||||||
|
|
@ -97,7 +110,7 @@ class TestEmailObject(unittest.TestCase):
|
||||||
self.assertEqual(self._get_values(bom_email_object, "received-header-ip"),
|
self.assertEqual(self._get_values(bom_email_object, "received-header-ip"),
|
||||||
self._get_values(eml_email_object, "received-header-ip"))
|
self._get_values(eml_email_object, "received-header-ip"))
|
||||||
|
|
||||||
def test_handling_of_various_email_types(self):
|
def test_handling_of_various_email_types(self) -> None:
|
||||||
self._does_not_fail(Path("tests/email_testfiles/mail_2.eml"),
|
self._does_not_fail(Path("tests/email_testfiles/mail_2.eml"),
|
||||||
"ensuring all headers work")
|
"ensuring all headers work")
|
||||||
self._does_not_fail(Path('tests/email_testfiles/mail_3.eml'),
|
self._does_not_fail(Path('tests/email_testfiles/mail_3.eml'),
|
||||||
|
|
@ -109,7 +122,7 @@ class TestEmailObject(unittest.TestCase):
|
||||||
self._does_not_fail(Path('tests/email_testfiles/mail_5.msg'),
|
self._does_not_fail(Path('tests/email_testfiles/mail_5.msg'),
|
||||||
"Check encapsulated HTML works")
|
"Check encapsulated HTML works")
|
||||||
|
|
||||||
def _does_not_fail(self, path, test_type="test"):
|
def _does_not_fail(self, path: Path, test_type: str="test") -> None:
|
||||||
found_error = None
|
found_error = None
|
||||||
try:
|
try:
|
||||||
EMailObject(path)
|
EMailObject(path)
|
||||||
|
|
@ -121,7 +134,7 @@ class TestEmailObject(unittest.TestCase):
|
||||||
path,
|
path,
|
||||||
test_type))
|
test_type))
|
||||||
|
|
||||||
def test_random_binary_blob(self):
|
def test_random_binary_blob(self) -> None:
|
||||||
"""Email parser fails correctly on random binary blob."""
|
"""Email parser fails correctly on random binary blob."""
|
||||||
random_data = urandom(1024)
|
random_data = urandom(1024)
|
||||||
random_blob = BytesIO(random_data)
|
random_blob = BytesIO(random_data)
|
||||||
|
|
@ -136,10 +149,9 @@ class TestEmailObject(unittest.TestCase):
|
||||||
broken_obj = EMailObject(pseudofile=random_blob)
|
broken_obj = EMailObject(pseudofile=random_blob)
|
||||||
except Exception as _e:
|
except Exception as _e:
|
||||||
found_error = _e
|
found_error = _e
|
||||||
if not isinstance(found_error, PyMISPNotImplementedYet):
|
if not isinstance(found_error, InvalidMISPObject):
|
||||||
self.fail("Expected PyMISPNotImplementedYet when EmailObject receives completely unknown binary input data in a pseudofile. But, did not get that exception.")
|
self.fail("Expected InvalidMISPObject when EmailObject receives completely unknown binary input data in a pseudofile. But, did not get that exception.")
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_values(obj: EMailObject, relation: str) -> List[str]:
|
def _get_values(obj: EMailObject, relation: str) -> list[str]:
|
||||||
return [attr.value for attr in obj.attributes if attr['object_relation'] == relation]
|
return [attr.value for attr in obj.attributes if attr['object_relation'] == relation]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import json
|
import json
|
||||||
|
|
@ -8,7 +9,7 @@ import pathlib
|
||||||
|
|
||||||
|
|
||||||
class TestFileObject(unittest.TestCase):
|
class TestFileObject(unittest.TestCase):
|
||||||
def test_mimeType(self):
|
def test_mimeType(self) -> None:
|
||||||
file_object = FileObject(filepath=pathlib.Path(__file__))
|
file_object = FileObject(filepath=pathlib.Path(__file__))
|
||||||
attributes = json.loads(file_object.to_json())['Attribute']
|
attributes = json.loads(file_object.to_json())['Attribute']
|
||||||
mime = next(attr for attr in attributes if attr['object_relation'] == 'mimetype')
|
mime = next(attr for attr in attributes if attr['object_relation'] == 'mimetype')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import json
|
import json
|
||||||
|
|
@ -16,89 +17,89 @@ from pymisp.tools import GitVulnFinderObject
|
||||||
|
|
||||||
class TestMISPEvent(unittest.TestCase):
|
class TestMISPEvent(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self) -> None:
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
self.mispevent = MISPEvent()
|
self.mispevent = MISPEvent()
|
||||||
|
|
||||||
def init_event(self):
|
def init_event(self) -> None:
|
||||||
self.mispevent.info = 'This is a test'
|
self.mispevent.info = 'This is a test'
|
||||||
self.mispevent.distribution = 1
|
self.mispevent.distribution = 1
|
||||||
self.mispevent.threat_level_id = 1
|
self.mispevent.threat_level_id = 1
|
||||||
self.mispevent.analysis = 1
|
self.mispevent.analysis = 1
|
||||||
self.mispevent.set_date("2017-12-31") # test the set date method
|
self.mispevent.set_date("2017-12-31") # test the set date method
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self) -> None:
|
||||||
with open('tests/mispevent_testfiles/simple.json', 'r') as f:
|
with open('tests/mispevent_testfiles/simple.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_event(self):
|
def test_event(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
self.mispevent.publish()
|
self.mispevent.publish()
|
||||||
with open('tests/mispevent_testfiles/event.json', 'r') as f:
|
with open('tests/mispevent_testfiles/event.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_loadfile(self):
|
def test_loadfile(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/event.json')
|
||||||
with open('tests/mispevent_testfiles/event.json', 'r') as f:
|
with open('tests/mispevent_testfiles/event.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_loadfile_validate(self):
|
def test_loadfile_validate(self) -> None:
|
||||||
misp_event = MISPEvent()
|
misp_event = MISPEvent()
|
||||||
misp_event.load_file('tests/mispevent_testfiles/event.json', validate=True)
|
misp_event.load_file('tests/mispevent_testfiles/event.json', validate=True)
|
||||||
|
|
||||||
def test_loadfile_validate_strict(self):
|
def test_loadfile_validate_strict(self) -> None:
|
||||||
misp_event = MISPEvent(strict_validation=True)
|
misp_event = MISPEvent(strict_validation=True)
|
||||||
misp_event.load_file('tests/mispevent_testfiles/event.json', validate=True)
|
misp_event.load_file('tests/mispevent_testfiles/event.json', validate=True)
|
||||||
|
|
||||||
def test_event_tag(self):
|
def test_event_tag(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
self.mispevent.add_tag('bar')
|
self.mispevent.add_tag('bar')
|
||||||
self.mispevent.add_tag(name='baz')
|
self.mispevent.add_tag(name='baz')
|
||||||
new_tag = MISPTag()
|
new_tag = MISPTag()
|
||||||
new_tag.from_dict(name='foo')
|
new_tag.from_dict(name='foo')
|
||||||
self.mispevent.add_tag(new_tag)
|
self.mispevent.add_tag(new_tag)
|
||||||
with open('tests/mispevent_testfiles/event_tags.json', 'r') as f:
|
with open('tests/mispevent_testfiles/event_tags.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_event_galaxy(self):
|
def test_event_galaxy(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
with open('tests/mispevent_testfiles/galaxy.json', 'r') as f:
|
with open('tests/mispevent_testfiles/galaxy.json') as f:
|
||||||
galaxy = json.load(f)
|
galaxy = json.load(f)
|
||||||
misp_galaxy = MISPGalaxy()
|
misp_galaxy = MISPGalaxy()
|
||||||
misp_galaxy.from_dict(**galaxy)
|
misp_galaxy.from_dict(**galaxy)
|
||||||
self.mispevent.add_galaxy(misp_galaxy)
|
self.mispevent.add_galaxy(misp_galaxy)
|
||||||
self.assertEqual(self.mispevent.galaxies[0].to_json(sort_keys=True, indent=2), json.dumps(galaxy, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.galaxies[0].to_json(sort_keys=True, indent=2), json.dumps(galaxy, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_attribute(self):
|
def test_attribute(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
a = self.mispevent.add_attribute('filename', 'bar.exe')
|
a: MISPAttribute = self.mispevent.add_attribute('filename', 'bar.exe') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
a = self.mispevent.add_attribute_tag('osint', 'bar.exe')
|
a = self.mispevent.add_attribute_tag('osint', 'bar.exe') # type: ignore[assignment]
|
||||||
attr_tags = self.mispevent.get_attribute_tag('bar.exe')
|
attr_tags = self.mispevent.get_attribute_tag('bar.exe')
|
||||||
self.assertEqual(self.mispevent.attributes[0].tags[0].name, 'osint')
|
self.assertEqual(self.mispevent.attributes[0].tags[0].name, 'osint')
|
||||||
self.assertEqual(attr_tags[0].name, 'osint')
|
self.assertEqual(attr_tags[0].name, 'osint')
|
||||||
with open('tests/mispevent_testfiles/attribute.json', 'r') as f:
|
with open('tests/mispevent_testfiles/attribute.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
# Fake setting an attribute ID for testing
|
# Fake setting an attribute ID for testing
|
||||||
self.mispevent.attributes[0].id = 42
|
self.mispevent.attributes[0].id = 42
|
||||||
self.mispevent.delete_attribute(42)
|
self.mispevent.delete_attribute('42')
|
||||||
with open('tests/mispevent_testfiles/attribute_del.json', 'r') as f:
|
with open('tests/mispevent_testfiles/attribute_del.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_attribute_galaxy(self):
|
def test_attribute_galaxy(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
with open('tests/mispevent_testfiles/galaxy.json', 'r') as f:
|
with open('tests/mispevent_testfiles/galaxy.json') as f:
|
||||||
galaxy = json.load(f)
|
galaxy = json.load(f)
|
||||||
misp_galaxy = MISPGalaxy()
|
misp_galaxy = MISPGalaxy()
|
||||||
misp_galaxy.from_dict(**galaxy)
|
misp_galaxy.from_dict(**galaxy)
|
||||||
|
|
@ -111,7 +112,7 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
json.dumps(galaxy, sort_keys=True, indent=2)
|
json.dumps(galaxy, sort_keys=True, indent=2)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_to_dict_json_format(self):
|
def test_to_dict_json_format(self) -> None:
|
||||||
misp_event = MISPEvent()
|
misp_event = MISPEvent()
|
||||||
av_signature_object = MISPObject("av-signature")
|
av_signature_object = MISPObject("av-signature")
|
||||||
av_signature_object.add_attribute("signature", "EICAR")
|
av_signature_object.add_attribute("signature", "EICAR")
|
||||||
|
|
@ -120,47 +121,47 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(json.loads(misp_event.to_json()), misp_event.to_dict(json_format=True))
|
self.assertEqual(json.loads(misp_event.to_json()), misp_event.to_dict(json_format=True))
|
||||||
|
|
||||||
def test_object_tag(self):
|
def test_object_tag(self) -> None:
|
||||||
self.mispevent.add_object(name='file', strict=True)
|
self.mispevent.add_object(name='file', strict=True)
|
||||||
a = self.mispevent.objects[0].add_attribute('filename', value='')
|
a: MISPAttribute = self.mispevent.objects[0].add_attribute('filename', value='') # type: ignore[assignment]
|
||||||
self.assertEqual(a, None)
|
self.assertEqual(a, None)
|
||||||
a = self.mispevent.objects[0].add_attribute('filename', value=None)
|
a = self.mispevent.objects[0].add_attribute('filename', value=None) # type: ignore[assignment]
|
||||||
self.assertEqual(a, None)
|
self.assertEqual(a, None)
|
||||||
a = self.mispevent.objects[0].add_attribute('filename', value='bar', Tag=[{'name': 'blah'}])
|
a = self.mispevent.objects[0].add_attribute('filename', value='bar', Tag=[{'name': 'blah'}]) # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
self.assertEqual(self.mispevent.objects[0].attributes[0].tags[0].name, 'blah')
|
self.assertEqual(self.mispevent.objects[0].attributes[0].tags[0].name, 'blah')
|
||||||
self.assertTrue(self.mispevent.objects[0].has_attributes_by_relation(['filename']))
|
self.assertTrue(self.mispevent.objects[0].has_attributes_by_relation(['filename']))
|
||||||
self.assertEqual(len(self.mispevent.objects[0].get_attributes_by_relation('filename')), 1)
|
self.assertEqual(len(self.mispevent.objects[0].get_attributes_by_relation('filename')), 1)
|
||||||
self.mispevent.add_object(name='url', strict=True)
|
self.mispevent.add_object(name='url', strict=True)
|
||||||
a = self.mispevent.objects[1].add_attribute('url', value='https://www.circl.lu')
|
a = self.mispevent.objects[1].add_attribute('url', value='https://www.circl.lu') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
self.mispevent.objects[0].uuid = 'a'
|
self.mispevent.objects[0].uuid = 'a'
|
||||||
self.mispevent.objects[1].uuid = 'b'
|
self.mispevent.objects[1].uuid = 'b'
|
||||||
reference = self.mispevent.objects[0].add_reference(self.mispevent.objects[1], 'baz', comment='foo')
|
reference = self.mispevent.objects[0].add_reference(self.mispevent.objects[1], 'baz', comment='foo')
|
||||||
del reference.uuid
|
del reference.uuid
|
||||||
self.assertEqual(self.mispevent.objects[0].references[0].relationship_type, 'baz')
|
self.assertEqual(self.mispevent.objects[0].references[0].relationship_type, 'baz')
|
||||||
with open('tests/mispevent_testfiles/event_obj_attr_tag.json', 'r') as f:
|
with open('tests/mispevent_testfiles/event_obj_attr_tag.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
@unittest.skip("Not supported on MISP: https://github.com/MISP/MISP/issues/2638 - https://github.com/MISP/PyMISP/issues/168")
|
@unittest.skip("Not supported on MISP: https://github.com/MISP/MISP/issues/2638 - https://github.com/MISP/PyMISP/issues/168")
|
||||||
def test_object_level_tag(self):
|
def test_object_level_tag(self) -> None:
|
||||||
self.mispevent.add_object(name='file', strict=True)
|
self.mispevent.add_object(name='file', strict=True)
|
||||||
self.mispevent.objects[0].add_attribute('filename', value='bar')
|
self.mispevent.objects[0].add_attribute('filename', value='bar')
|
||||||
self.mispevent.objects[0].add_tag('osint')
|
self.mispevent.objects[0].add_tag('osint') # type: ignore[attr-defined]
|
||||||
self.mispevent.objects[0].uuid = 'a'
|
self.mispevent.objects[0].uuid = 'a'
|
||||||
with open('tests/mispevent_testfiles/event_obj_tag.json', 'r') as f:
|
with open('tests/mispevent_testfiles/event_obj_tag.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_object_galaxy(self):
|
def test_object_galaxy(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
misp_object = MISPObject('github-user')
|
misp_object = MISPObject('github-user')
|
||||||
misp_object.add_attribute('username', 'adulau')
|
misp_object.add_attribute('username', 'adulau')
|
||||||
misp_object.add_attribute('repository', 'cve-search')
|
misp_object.add_attribute('repository', 'cve-search')
|
||||||
self.mispevent.add_object(misp_object)
|
self.mispevent.add_object(misp_object)
|
||||||
with open('tests/mispevent_testfiles/galaxy.json', 'r') as f:
|
with open('tests/mispevent_testfiles/galaxy.json') as f:
|
||||||
galaxy = json.load(f)
|
galaxy = json.load(f)
|
||||||
misp_galaxy = MISPGalaxy()
|
misp_galaxy = MISPGalaxy()
|
||||||
misp_galaxy.from_dict(**galaxy)
|
misp_galaxy.from_dict(**galaxy)
|
||||||
|
|
@ -170,95 +171,95 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
json.dumps(galaxy, sort_keys=True, indent=2)
|
json.dumps(galaxy, sort_keys=True, indent=2)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_malware(self):
|
def test_malware(self) -> None:
|
||||||
with open('tests/mispevent_testfiles/simple.json', 'rb') as f:
|
with open('tests/mispevent_testfiles/simple.json', 'rb') as f:
|
||||||
pseudofile = BytesIO(f.read())
|
pseudofile = BytesIO(f.read())
|
||||||
self.init_event()
|
self.init_event()
|
||||||
a = self.mispevent.add_attribute('malware-sample', 'bar.exe', data=pseudofile)
|
a: MISPAttribute = self.mispevent.add_attribute('malware-sample', 'bar.exe', data=pseudofile) # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
attribute = self.mispevent.attributes[0]
|
attribute = self.mispevent.attributes[0]
|
||||||
self.assertEqual(attribute.malware_binary, pseudofile)
|
self.assertEqual(attribute.malware_binary, pseudofile)
|
||||||
with open('tests/mispevent_testfiles/malware.json', 'r') as f:
|
with open('tests/mispevent_testfiles/malware.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_existing_malware(self):
|
def test_existing_malware(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/malware_exist.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/malware_exist.json')
|
||||||
with open('tests/mispevent_testfiles/simple.json', 'rb') as f:
|
with open('tests/mispevent_testfiles/simple.json', 'rb') as f:
|
||||||
pseudofile = BytesIO(f.read())
|
pseudofile = BytesIO(f.read())
|
||||||
self.assertEqual(
|
self.assertTrue(self.mispevent.objects[0].get_attributes_by_relation('malware-sample')[0].malware_binary)
|
||||||
self.mispevent.objects[0].get_attributes_by_relation('malware-sample')[0].malware_binary.read(),
|
if _mb := self.mispevent.objects[0].get_attributes_by_relation('malware-sample')[0].malware_binary:
|
||||||
pseudofile.read())
|
self.assertEqual(_mb.read(), pseudofile.read())
|
||||||
|
|
||||||
def test_sighting(self):
|
def test_sighting(self) -> None:
|
||||||
sighting = MISPSighting()
|
sighting = MISPSighting()
|
||||||
sighting.from_dict(value='1', type='bar', timestamp=11111111)
|
sighting.from_dict(value='1', type='bar', timestamp=11111111)
|
||||||
with open('tests/mispevent_testfiles/sighting.json', 'r') as f:
|
with open('tests/mispevent_testfiles/sighting.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
self.assertEqual(sighting.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(sighting.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_existing_event(self):
|
def test_existing_event(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
with open('tests/mispevent_testfiles/existing_event.json', 'r') as f:
|
with open('tests/mispevent_testfiles/existing_event.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
|
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_shadow_attributes_existing(self):
|
def test_shadow_attributes_existing(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/shadow.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/shadow.json')
|
||||||
with open('tests/mispevent_testfiles/shadow.json', 'r') as f:
|
with open('tests/mispevent_testfiles/shadow.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
@unittest.skip("Not supported on MISP.")
|
@unittest.skip("Not supported on MISP.")
|
||||||
def test_shadow_attributes(self):
|
def test_shadow_attributes(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
p = self.mispevent.add_proposal(type='filename', value='baz.jpg')
|
p = self.mispevent.add_proposal(type='filename', value='baz.jpg')
|
||||||
del p.uuid
|
del p.uuid
|
||||||
a = self.mispevent.add_attribute('filename', 'bar.exe')
|
a: MISPAttribute = self.mispevent.add_attribute('filename', 'bar.exe') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
p = self.mispevent.attributes[0].add_proposal(type='filename', value='bar.pdf')
|
p = self.mispevent.attributes[0].add_proposal(type='filename', value='bar.pdf')
|
||||||
del p.uuid
|
del p.uuid
|
||||||
with open('tests/mispevent_testfiles/proposals.json', 'r') as f:
|
with open('tests/mispevent_testfiles/proposals.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_default_attributes(self):
|
def test_default_attributes(self) -> None:
|
||||||
self.mispevent.add_object(name='file', strict=True)
|
self.mispevent.add_object(name='file', strict=True)
|
||||||
a = self.mispevent.objects[0].add_attribute('filename', value='bar', Tag=[{'name': 'blah'}])
|
a: MISPAttribute = self.mispevent.objects[0].add_attribute('filename', value='bar', Tag=[{'name': 'blah'}]) # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
a = self.mispevent.objects[0].add_attribute('pattern-in-file', value='baz')
|
a = self.mispevent.objects[0].add_attribute('pattern-in-file', value='baz') # type: ignore[assignment]
|
||||||
self.assertEqual(a.category, 'Artifacts dropped')
|
self.assertEqual(a.category, 'Artifacts dropped')
|
||||||
del a.uuid
|
del a.uuid
|
||||||
self.mispevent.add_object(name='file', strict=False, default_attributes_parameters=self.mispevent.objects[0].attributes[0])
|
self.mispevent.add_object(name='file', strict=False, default_attributes_parameters=self.mispevent.objects[0].attributes[0])
|
||||||
a = self.mispevent.objects[1].add_attribute('filename', value='baz')
|
a = self.mispevent.objects[1].add_attribute('filename', value='baz') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
self.mispevent.objects[0].uuid = 'a'
|
self.mispevent.objects[0].uuid = 'a'
|
||||||
self.mispevent.objects[1].uuid = 'b'
|
self.mispevent.objects[1].uuid = 'b'
|
||||||
with open('tests/mispevent_testfiles/event_obj_def_param.json', 'r') as f:
|
with open('tests/mispevent_testfiles/event_obj_def_param.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_obj_default_values(self):
|
def test_obj_default_values(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
self.mispevent.add_object(name='whois', strict=True)
|
self.mispevent.add_object(name='whois', strict=True)
|
||||||
a = self.mispevent.objects[0].add_attribute('registrar', value='registar.example.com')
|
a: MISPAttribute = self.mispevent.objects[0].add_attribute('registrar', value='registar.example.com') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
a = self.mispevent.objects[0].add_attribute('domain', value='domain.example.com')
|
a = self.mispevent.objects[0].add_attribute('domain', value='domain.example.com') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
a = self.mispevent.objects[0].add_attribute('nameserver', value='ns1.example.com')
|
a = self.mispevent.objects[0].add_attribute('nameserver', value='ns1.example.com') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
a = self.mispevent.objects[0].add_attribute('nameserver', value='ns2.example.com', disable_correlation=False, to_ids=True, category='External analysis')
|
a = self.mispevent.objects[0].add_attribute('nameserver', value='ns2.example.com', disable_correlation=False, to_ids=True, category='External analysis') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
self.mispevent.objects[0].uuid = 'a'
|
self.mispevent.objects[0].uuid = 'a'
|
||||||
with open('tests/mispevent_testfiles/def_param.json', 'r') as f:
|
with open('tests/mispevent_testfiles/def_param.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_obj_references_export(self):
|
def test_obj_references_export(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
obj1 = MISPObject(name="file")
|
obj1 = MISPObject(name="file")
|
||||||
obj2 = MISPObject(name="url", standalone=False)
|
obj2 = MISPObject(name="url", standalone=False)
|
||||||
|
|
@ -271,29 +272,29 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
self.assertTrue("ObjectReference" in obj1.jsonable())
|
self.assertTrue("ObjectReference" in obj1.jsonable())
|
||||||
self.assertFalse("ObjectReference" in obj2.jsonable())
|
self.assertFalse("ObjectReference" in obj2.jsonable())
|
||||||
|
|
||||||
def test_event_not_edited(self):
|
def test_event_not_edited(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.assertFalse(self.mispevent.edited)
|
self.assertFalse(self.mispevent.edited)
|
||||||
|
|
||||||
def test_event_edited(self):
|
def test_event_edited(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.mispevent.info = 'blah'
|
self.mispevent.info = 'blah'
|
||||||
self.assertTrue(self.mispevent.edited)
|
self.assertTrue(self.mispevent.edited)
|
||||||
|
|
||||||
def test_event_tag_edited(self):
|
def test_event_tag_edited(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.assertFalse(self.mispevent.edited)
|
self.assertFalse(self.mispevent.edited)
|
||||||
self.mispevent.add_tag('foo')
|
self.mispevent.add_tag('foo')
|
||||||
self.assertTrue(self.mispevent.edited)
|
self.assertTrue(self.mispevent.edited)
|
||||||
|
|
||||||
def test_event_attribute_edited(self):
|
def test_event_attribute_edited(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.mispevent.attributes[0].value = 'blah'
|
self.mispevent.attributes[0].value = 'blah'
|
||||||
self.assertTrue(self.mispevent.attributes[0].edited)
|
self.assertTrue(self.mispevent.attributes[0].edited)
|
||||||
self.assertFalse(self.mispevent.attributes[1].edited)
|
self.assertFalse(self.mispevent.attributes[1].edited)
|
||||||
self.assertTrue(self.mispevent.edited)
|
self.assertTrue(self.mispevent.edited)
|
||||||
|
|
||||||
def test_event_attribute_tag_edited(self):
|
def test_event_attribute_tag_edited(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.assertFalse(self.mispevent.edited)
|
self.assertFalse(self.mispevent.edited)
|
||||||
self.mispevent.attributes[0].tags[0].name = 'blah'
|
self.mispevent.attributes[0].tags[0].name = 'blah'
|
||||||
|
|
@ -302,14 +303,14 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
self.assertTrue(self.mispevent.attributes[0].edited)
|
self.assertTrue(self.mispevent.attributes[0].edited)
|
||||||
self.assertTrue(self.mispevent.edited)
|
self.assertTrue(self.mispevent.edited)
|
||||||
|
|
||||||
def test_event_attribute_tag_edited_second(self):
|
def test_event_attribute_tag_edited_second(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.assertFalse(self.mispevent.edited)
|
self.assertFalse(self.mispevent.edited)
|
||||||
self.mispevent.attributes[0].add_tag(name='blah')
|
self.mispevent.attributes[0].add_tag(name='blah')
|
||||||
self.assertTrue(self.mispevent.attributes[0].edited)
|
self.assertTrue(self.mispevent.attributes[0].edited)
|
||||||
self.assertTrue(self.mispevent.edited)
|
self.assertTrue(self.mispevent.edited)
|
||||||
|
|
||||||
def test_event_object_edited(self):
|
def test_event_object_edited(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.assertFalse(self.mispevent.edited)
|
self.assertFalse(self.mispevent.edited)
|
||||||
self.mispevent.objects[0].comment = 'blah'
|
self.mispevent.objects[0].comment = 'blah'
|
||||||
|
|
@ -317,7 +318,7 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
self.assertFalse(self.mispevent.objects[1].edited)
|
self.assertFalse(self.mispevent.objects[1].edited)
|
||||||
self.assertTrue(self.mispevent.edited)
|
self.assertTrue(self.mispevent.edited)
|
||||||
|
|
||||||
def test_event_object_attribute_edited(self):
|
def test_event_object_attribute_edited(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.assertFalse(self.mispevent.edited)
|
self.assertFalse(self.mispevent.edited)
|
||||||
self.mispevent.objects[0].attributes[0].comment = 'blah'
|
self.mispevent.objects[0].attributes[0].comment = 'blah'
|
||||||
|
|
@ -325,23 +326,23 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
self.assertTrue(self.mispevent.objects[0].edited)
|
self.assertTrue(self.mispevent.objects[0].edited)
|
||||||
self.assertTrue(self.mispevent.edited)
|
self.assertTrue(self.mispevent.edited)
|
||||||
|
|
||||||
def test_event_object_attribute_edited_tag(self):
|
def test_event_object_attribute_edited_tag(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
self.assertFalse(self.mispevent.edited)
|
self.assertFalse(self.mispevent.edited)
|
||||||
self.mispevent.objects[0].attributes[0].add_tag('blah')
|
self.mispevent.objects[0].attributes[0].add_tag('blah')
|
||||||
self.assertTrue(self.mispevent.objects[0].attributes[0].edited)
|
self.assertTrue(self.mispevent.objects[0].attributes[0].edited)
|
||||||
self.assertTrue(self.mispevent.objects[0].edited)
|
self.assertTrue(self.mispevent.objects[0].edited)
|
||||||
self.assertTrue(self.mispevent.edited)
|
self.assertTrue(self.mispevent.edited)
|
||||||
with open('tests/mispevent_testfiles/existing_event_edited.json', 'r') as f:
|
with open('tests/mispevent_testfiles/existing_event_edited.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_obj_by_id(self):
|
def test_obj_by_id(self) -> None:
|
||||||
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
self.mispevent.load_file('tests/mispevent_testfiles/existing_event.json')
|
||||||
misp_obj = self.mispevent.get_object_by_id(1556)
|
misp_obj = self.mispevent.get_object_by_id(1556)
|
||||||
self.assertEqual(misp_obj.uuid, '5a3cd604-e11c-4de5-bbbf-c170950d210f')
|
self.assertEqual(misp_obj.uuid, '5a3cd604-e11c-4de5-bbbf-c170950d210f')
|
||||||
|
|
||||||
def test_userdefined_object_custom_template(self):
|
def test_userdefined_object_custom_template(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
with open('tests/mispevent_testfiles/test_object_template/definition.json') as f:
|
with open('tests/mispevent_testfiles/test_object_template/definition.json') as f:
|
||||||
template = json.load(f)
|
template = json.load(f)
|
||||||
|
|
@ -352,16 +353,16 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
self.mispevent.to_json(sort_keys=True, indent=2)
|
self.mispevent.to_json(sort_keys=True, indent=2)
|
||||||
self.assertEqual(e.exception.message, '{\'member3\'} are required.')
|
self.assertEqual(e.exception.message, '{\'member3\'} are required.')
|
||||||
|
|
||||||
a = self.mispevent.objects[0].add_attribute('member3', value='foo')
|
a: MISPAttribute = self.mispevent.objects[0].add_attribute('member3', value='foo') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
with self.assertRaises(InvalidMISPObject) as e:
|
with self.assertRaises(InvalidMISPObject) as e:
|
||||||
# Fail on requiredOneOf
|
# Fail on requiredOneOf
|
||||||
self.mispevent.to_json(sort_keys=True, indent=2)
|
self.mispevent.to_json(sort_keys=True, indent=2)
|
||||||
self.assertEqual(e.exception.message, 'At least one of the following attributes is required: member1, member2')
|
self.assertEqual(e.exception.message, 'At least one of the following attributes is required: member1, member2')
|
||||||
|
|
||||||
a = self.mispevent.objects[0].add_attribute('member1', value='bar')
|
a = self.mispevent.objects[0].add_attribute('member1', value='bar') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
a = self.mispevent.objects[0].add_attribute('member1', value='baz')
|
a = self.mispevent.objects[0].add_attribute('member1', value='baz') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
with self.assertRaises(InvalidMISPObject) as e:
|
with self.assertRaises(InvalidMISPObject) as e:
|
||||||
# member1 is not a multiple
|
# member1 is not a multiple
|
||||||
|
|
@ -370,12 +371,12 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
|
|
||||||
self.mispevent.objects[0].attributes = self.mispevent.objects[0].attributes[:2]
|
self.mispevent.objects[0].attributes = self.mispevent.objects[0].attributes[:2]
|
||||||
self.mispevent.objects[0].uuid = 'a'
|
self.mispevent.objects[0].uuid = 'a'
|
||||||
with open('tests/mispevent_testfiles/misp_custom_obj.json', 'r') as f:
|
with open('tests/mispevent_testfiles/misp_custom_obj.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_userdefined_object_custom_dir(self):
|
def test_userdefined_object_custom_dir(self) -> None:
|
||||||
self.init_event()
|
self.init_event()
|
||||||
self.mispevent.add_object(name='test_object_template', strict=True, misp_objects_path_custom='tests/mispevent_testfiles')
|
self.mispevent.add_object(name='test_object_template', strict=True, misp_objects_path_custom='tests/mispevent_testfiles')
|
||||||
with self.assertRaises(InvalidMISPObject) as e:
|
with self.assertRaises(InvalidMISPObject) as e:
|
||||||
|
|
@ -383,16 +384,16 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
self.mispevent.to_json(sort_keys=True, indent=2)
|
self.mispevent.to_json(sort_keys=True, indent=2)
|
||||||
self.assertEqual(e.exception.message, '{\'member3\'} are required.')
|
self.assertEqual(e.exception.message, '{\'member3\'} are required.')
|
||||||
|
|
||||||
a = self.mispevent.objects[0].add_attribute('member3', value='foo')
|
a: MISPAttribute = self.mispevent.objects[0].add_attribute('member3', value='foo') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
with self.assertRaises(InvalidMISPObject) as e:
|
with self.assertRaises(InvalidMISPObject) as e:
|
||||||
# Fail on requiredOneOf
|
# Fail on requiredOneOf
|
||||||
self.mispevent.to_json(sort_keys=True, indent=2)
|
self.mispevent.to_json(sort_keys=True, indent=2)
|
||||||
self.assertEqual(e.exception.message, 'At least one of the following attributes is required: member1, member2')
|
self.assertEqual(e.exception.message, 'At least one of the following attributes is required: member1, member2')
|
||||||
|
|
||||||
a = self.mispevent.objects[0].add_attribute('member1', value='bar')
|
a = self.mispevent.objects[0].add_attribute('member1', value='bar') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
a = self.mispevent.objects[0].add_attribute('member1', value='baz')
|
a = self.mispevent.objects[0].add_attribute('member1', value='baz') # type: ignore[assignment]
|
||||||
del a.uuid
|
del a.uuid
|
||||||
with self.assertRaises(InvalidMISPObject) as e:
|
with self.assertRaises(InvalidMISPObject) as e:
|
||||||
# member1 is not a multiple
|
# member1 is not a multiple
|
||||||
|
|
@ -401,15 +402,15 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
|
|
||||||
self.mispevent.objects[0].attributes = self.mispevent.objects[0].attributes[:2]
|
self.mispevent.objects[0].attributes = self.mispevent.objects[0].attributes[:2]
|
||||||
self.mispevent.objects[0].uuid = 'a'
|
self.mispevent.objects[0].uuid = 'a'
|
||||||
with open('tests/mispevent_testfiles/misp_custom_obj.json', 'r') as f:
|
with open('tests/mispevent_testfiles/misp_custom_obj.json') as f:
|
||||||
ref_json = json.load(f)
|
ref_json = json.load(f)
|
||||||
del self.mispevent.uuid
|
del self.mispevent.uuid
|
||||||
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
self.assertEqual(self.mispevent.to_json(sort_keys=True, indent=2), json.dumps(ref_json, sort_keys=True, indent=2))
|
||||||
|
|
||||||
def test_first_last_seen(self):
|
def test_first_last_seen(self) -> None:
|
||||||
me = MISPEvent()
|
me = MISPEvent()
|
||||||
me.info = 'Test First and Last Seen'
|
me.info = 'Test First and Last Seen'
|
||||||
me.date = '2020.01.12'
|
me.date = '2020.01.12' # type: ignore[assignment]
|
||||||
self.assertEqual(me.date.day, 12)
|
self.assertEqual(me.date.day, 12)
|
||||||
me.add_attribute('ip-dst', '8.8.8.8', first_seen='06-21-1998', last_seen=1580213607.469571)
|
me.add_attribute('ip-dst', '8.8.8.8', first_seen='06-21-1998', last_seen=1580213607.469571)
|
||||||
self.assertEqual(me.attributes[0].first_seen.year, 1998)
|
self.assertEqual(me.attributes[0].first_seen.year, 1998)
|
||||||
|
|
@ -417,11 +418,11 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
now = datetime.now().astimezone()
|
now = datetime.now().astimezone()
|
||||||
me.attributes[0].last_seen = now
|
me.attributes[0].last_seen = now
|
||||||
today = date.today()
|
today = date.today()
|
||||||
me.attributes[0].first_seen = today
|
me.attributes[0].first_seen = today # type: ignore[assignment]
|
||||||
self.assertEqual(me.attributes[0].first_seen.year, today.year)
|
self.assertEqual(me.attributes[0].first_seen.year, today.year)
|
||||||
self.assertEqual(me.attributes[0].last_seen, now)
|
self.assertEqual(me.attributes[0].last_seen, now)
|
||||||
|
|
||||||
def test_feed(self):
|
def test_feed(self) -> None:
|
||||||
me = MISPEvent()
|
me = MISPEvent()
|
||||||
me.info = 'Test feed'
|
me.info = 'Test feed'
|
||||||
org = MISPOrganisation()
|
org = MISPOrganisation()
|
||||||
|
|
@ -439,7 +440,7 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
self.assertEqual(feed['Event']['_manifest'][me.uuid]['info'], 'Test feed')
|
self.assertEqual(feed['Event']['_manifest'][me.uuid]['info'], 'Test feed')
|
||||||
self.assertEqual(len(feed['Event']['Object'][0]['Attribute']), 2)
|
self.assertEqual(len(feed['Event']['Object'][0]['Attribute']), 2)
|
||||||
|
|
||||||
def test_object_templates(self):
|
def test_object_templates(self) -> None:
|
||||||
me = MISPEvent()
|
me = MISPEvent()
|
||||||
for template in glob.glob(str(me.misp_objects_path / '*' / 'definition.json')):
|
for template in glob.glob(str(me.misp_objects_path / '*' / 'definition.json')):
|
||||||
with open(template) as f:
|
with open(template) as f:
|
||||||
|
|
@ -458,7 +459,7 @@ class TestMISPEvent(unittest.TestCase):
|
||||||
subset = set(entry['categories']).issubset(me.describe_types['categories'])
|
subset = set(entry['categories']).issubset(me.describe_types['categories'])
|
||||||
self.assertTrue(subset, f'{t_json["name"]} - {obj_relation}')
|
self.assertTrue(subset, f'{t_json["name"]} - {obj_relation}')
|
||||||
|
|
||||||
def test_git_vuln_finder(self):
|
def test_git_vuln_finder(self) -> None:
|
||||||
with open('tests/git-vuln-finder-quagga.json') as f:
|
with open('tests/git-vuln-finder-quagga.json') as f:
|
||||||
dump = json.load(f)
|
dump = json.load(f)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,23 +1,19 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import sys
|
|
||||||
import unittest
|
import unittest
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import urllib3 # type: ignore
|
import urllib3
|
||||||
import logging
|
import logging
|
||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pymisp import ExpandedPyMISP, MISPOrganisation, MISPUser, MISPEvent, MISPObject, MISPSharingGroup, Distribution
|
from pymisp import PyMISP, MISPOrganisation, MISPUser, MISPEvent, MISPObject, MISPSharingGroup, Distribution
|
||||||
except ImportError:
|
except ImportError:
|
||||||
if sys.version_info < (3, 6):
|
raise
|
||||||
print('This test suite requires Python 3.6+, breaking.')
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
key = 'eYQdGTEWZJ8C2lm9EpnMqxQGwGiPNyoR75JvLdlE'
|
key = 'eYQdGTEWZJ8C2lm9EpnMqxQGwGiPNyoR75JvLdlE'
|
||||||
verifycert = False
|
verifycert = False
|
||||||
|
|
@ -73,7 +69,7 @@ fast_mode = True
|
||||||
class MISPInstance():
|
class MISPInstance():
|
||||||
|
|
||||||
def __init__(self, params):
|
def __init__(self, params):
|
||||||
self.initial_user_connector = ExpandedPyMISP(params['url'], params['key'], ssl=False, debug=False)
|
self.initial_user_connector = PyMISP(params['url'], params['key'], ssl=False, debug=False)
|
||||||
# Git pull
|
# Git pull
|
||||||
self.initial_user_connector.update_misp()
|
self.initial_user_connector.update_misp()
|
||||||
# Set the default role (id 3 on the VM is normal user)
|
# Set the default role (id 3 on the VM is normal user)
|
||||||
|
|
@ -101,7 +97,7 @@ class MISPInstance():
|
||||||
user.org_id = self.test_org.id
|
user.org_id = self.test_org.id
|
||||||
user.role_id = 1 # Site admin
|
user.role_id = 1 # Site admin
|
||||||
self.test_site_admin = self.initial_user_connector.add_user(user)
|
self.test_site_admin = self.initial_user_connector.add_user(user)
|
||||||
self.site_admin_connector = ExpandedPyMISP(params['url'], self.test_site_admin.authkey, ssl=False, debug=False)
|
self.site_admin_connector = PyMISP(params['url'], self.test_site_admin.authkey, ssl=False, debug=False)
|
||||||
self.site_admin_connector.toggle_global_pythonify()
|
self.site_admin_connector.toggle_global_pythonify()
|
||||||
# Create org admin
|
# Create org admin
|
||||||
user = MISPUser()
|
user = MISPUser()
|
||||||
|
|
@ -109,14 +105,14 @@ class MISPInstance():
|
||||||
user.org_id = self.test_org.id
|
user.org_id = self.test_org.id
|
||||||
user.role_id = 2 # Org admin
|
user.role_id = 2 # Org admin
|
||||||
self.test_org_admin = self.site_admin_connector.add_user(user)
|
self.test_org_admin = self.site_admin_connector.add_user(user)
|
||||||
self.org_admin_connector = ExpandedPyMISP(params['url'], self.test_org_admin.authkey, ssl=False, debug=False)
|
self.org_admin_connector = PyMISP(params['url'], self.test_org_admin.authkey, ssl=False, debug=False)
|
||||||
self.org_admin_connector.toggle_global_pythonify()
|
self.org_admin_connector.toggle_global_pythonify()
|
||||||
# Create user
|
# Create user
|
||||||
user = MISPUser()
|
user = MISPUser()
|
||||||
user.email = params['email_user']
|
user.email = params['email_user']
|
||||||
user.org_id = self.test_org.id
|
user.org_id = self.test_org.id
|
||||||
self.test_usr = self.org_admin_connector.add_user(user)
|
self.test_usr = self.org_admin_connector.add_user(user)
|
||||||
self.user_connector = ExpandedPyMISP(params['url'], self.test_usr.authkey, ssl=False, debug=False)
|
self.user_connector = PyMISP(params['url'], self.test_usr.authkey, ssl=False, debug=False)
|
||||||
self.user_connector.toggle_global_pythonify()
|
self.user_connector.toggle_global_pythonify()
|
||||||
|
|
||||||
# Setup external_baseurl
|
# Setup external_baseurl
|
||||||
|
|
@ -141,7 +137,7 @@ class MISPInstance():
|
||||||
user.org_id = sync_org.id
|
user.org_id = sync_org.id
|
||||||
user.role_id = 5 # Org admin
|
user.role_id = 5 # Org admin
|
||||||
sync_user = self.site_admin_connector.add_user(user)
|
sync_user = self.site_admin_connector.add_user(user)
|
||||||
sync_user_connector = ExpandedPyMISP(self.site_admin_connector.root_url, sync_user.authkey, ssl=False, debug=False)
|
sync_user_connector = PyMISP(self.site_admin_connector.root_url, sync_user.authkey, ssl=False, debug=False)
|
||||||
sync_server_config = sync_user_connector.get_sync_config(pythonify=True)
|
sync_server_config = sync_user_connector.get_sync_config(pythonify=True)
|
||||||
self.sync.append((sync_org, sync_user, sync_server_config))
|
self.sync.append((sync_org, sync_user, sync_server_config))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue