mirror of https://github.com/MISP/misp-modules
Merge branch 'main' of github.com:MISP/misp-modules
commit
f023c2ba5d
|
@ -3,7 +3,7 @@ aiohttp>=3.9.0
|
||||||
aiosignal==1.3.1 ; python_version >= '3.7'
|
aiosignal==1.3.1 ; python_version >= '3.7'
|
||||||
antlr4-python3-runtime==4.9.3
|
antlr4-python3-runtime==4.9.3
|
||||||
anyio==3.6.2 ; python_full_version >= '3.6.2'
|
anyio==3.6.2 ; python_full_version >= '3.6.2'
|
||||||
git+https://github.com/davidonzo/apiosintDS@misp
|
apiosintDS==2.0.3
|
||||||
appdirs==1.4.4
|
appdirs==1.4.4
|
||||||
argcomplete==3.0.8 ; python_version >= '3.6'
|
argcomplete==3.0.8 ; python_version >= '3.6'
|
||||||
argparse==1.4.0
|
argparse==1.4.0
|
||||||
|
|
|
@ -38,6 +38,7 @@ For more information: [Extending MISP with Python modules](https://www.circl.lu/
|
||||||
* [EQL](misp_modules/modules/expansion/eql.py) - an expansion module to generate event query language (EQL) from an attribute. [Event Query Language](https://eql.readthedocs.io/en/latest/)
|
* [EQL](misp_modules/modules/expansion/eql.py) - an expansion module to generate event query language (EQL) from an attribute. [Event Query Language](https://eql.readthedocs.io/en/latest/)
|
||||||
* [Farsight DNSDB Passive DNS](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/farsight_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information.
|
* [Farsight DNSDB Passive DNS](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/farsight_passivedns.py) - a hover and expansion module to expand hostname and IP addresses with passive DNS information.
|
||||||
* [GeoIP](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/geoip_country.py) - a hover and expansion module to get GeoIP information from geolite/maxmind.
|
* [GeoIP](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/geoip_country.py) - a hover and expansion module to get GeoIP information from geolite/maxmind.
|
||||||
|
* [Google Threat Intelligence] (https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/google_threat_intelligence.py) - An expansion module to have the observable's threat score assessed by Google Threat Intelligence.
|
||||||
* [Greynoise](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/greynoise.py) - a hover to get information from greynoise.
|
* [Greynoise](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/greynoise.py) - a hover to get information from greynoise.
|
||||||
* [hashdd](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/hashdd.py) - a hover module to check file hashes against [hashdd.com](http://www.hashdd.com) including NSLR dataset.
|
* [hashdd](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/hashdd.py) - a hover module to check file hashes against [hashdd.com](http://www.hashdd.com) including NSLR dataset.
|
||||||
* [hibp](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/hibp.py) - a hover module to lookup against Have I Been Pwned?
|
* [hibp](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/hibp.py) - a hover module to lookup against Have I Been Pwned?
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -640,6 +640,7 @@ Module to query a local copy of Maxmind's Geolite database.
|
||||||
#### [google_search](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/google_search.py)
|
#### [google_search](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/google_search.py)
|
||||||
|
|
||||||
<img src=logos/google.png height=60>
|
<img src=logos/google.png height=60>
|
||||||
|
|
||||||
- **descrption**:
|
- **descrption**:
|
||||||
>A hover module to get information about an url using a Google search.
|
>A hover module to get information about an url using a Google search.
|
||||||
- **features**:
|
- **features**:
|
||||||
|
@ -655,6 +656,27 @@ Module to query a local copy of Maxmind's Geolite database.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
#### [google_threat_intelligence](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/google_threat_intelligence.py)
|
||||||
|
|
||||||
|
<img src=logos/google_threat_intelligence.png height=60>
|
||||||
|
|
||||||
|
- **description**:
|
||||||
|
An expansion module to have the observable's threat score assessed by Google Threat Intelligence.
|
||||||
|
- **features**:
|
||||||
|
>The module gives the Google Threat Intelligence assessment including a verdict for the given obsevable. [Example screeshot](https://github.com/MISP/MISP/assets/4747608/e275db2f-bb1e-4413-8cc0-ec3cb05e0414)
|
||||||
|
]
|
||||||
|
- **input**:
|
||||||
|
>'hostname', 'domain', 'ip-src', 'ip-dst', 'md5', 'sha1', 'sha256', 'url'.
|
||||||
|
- **output**:
|
||||||
|
>Text fields containing the threat score, the severity, the verdict and the threat label of the observable inspected.
|
||||||
|
- **references**:
|
||||||
|
>https://gtidocs.virustotal.com/reference
|
||||||
|
- **requirements**:
|
||||||
|
>- pymisp
|
||||||
|
>- vt
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
#### [greynoise](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/greynoise.py)
|
#### [greynoise](https://github.com/MISP/misp-modules/tree/main/misp_modules/modules/expansion/greynoise.py)
|
||||||
|
|
||||||
<img src=logos/greynoise.png height=60>
|
<img src=logos/greynoise.png height=60>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"description": "An expansion module to have the observable's threat score assessed by Google Threat Intelligence.",
|
||||||
|
"logo": "google_threat_intelligence.png",
|
||||||
|
"requirements": [
|
||||||
|
"An access to the Google Threat Intelligence API (apikey), with a high request rate limit."
|
||||||
|
],
|
||||||
|
"input": "A domain, hash (md5, sha1, sha256 or sha512), hostname or IP address attribute.",
|
||||||
|
"output": "Text fields containing the threat score, the severity, the verdict and the threat label of the observable inspected.",
|
||||||
|
"references": [
|
||||||
|
"https://www.virustotal.com/",
|
||||||
|
"https://gtidocs.virustotal.com/reference"
|
||||||
|
],
|
||||||
|
"features": "GTI assessment for the given observable, this include information about level of severity, a clear verdict (malicious, suspicious, undetected and bening) and additional information provided by the Mandiant expertise combined with the VirusTotal database.\n\n[Output example screeshot](https://github.com/MISP/MISP/assets/4747608/e275db2f-bb1e-4413-8cc0-ec3cb05e0414)"
|
||||||
|
}
|
|
@ -20,7 +20,8 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c
|
||||||
'trustar_enrich', 'recordedfuture', 'html_to_markdown', 'socialscan', 'passive-ssh',
|
'trustar_enrich', 'recordedfuture', 'html_to_markdown', 'socialscan', 'passive-ssh',
|
||||||
'qintel_qsentry', 'mwdb', 'hashlookup', 'mmdb_lookup', 'ipqs_fraud_and_risk_scoring',
|
'qintel_qsentry', 'mwdb', 'hashlookup', 'mmdb_lookup', 'ipqs_fraud_and_risk_scoring',
|
||||||
'clamav', 'jinja_template_rendering','hyasinsight', 'variotdbs', 'crowdsec',
|
'clamav', 'jinja_template_rendering','hyasinsight', 'variotdbs', 'crowdsec',
|
||||||
'extract_url_components', 'ipinfo', 'whoisfreaks', 'ip2locationio', 'vysion', 'stairwell']
|
'extract_url_components', 'ipinfo', 'whoisfreaks', 'ip2locationio', 'vysion', 'stairwell',
|
||||||
|
'google_threat_intelligence']
|
||||||
|
|
||||||
|
|
||||||
minimum_required_fields = ('type', 'uuid', 'value')
|
minimum_required_fields = ('type', 'uuid', 'value')
|
||||||
|
|
|
@ -0,0 +1,322 @@
|
||||||
|
#!/usr/local/bin/python
|
||||||
|
# Copyright © 2024 The Google Threat Intelligence authors. All Rights Reserved.
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Google Threat Intelligence MISP expansion module."""
|
||||||
|
|
||||||
|
from urllib import parse
|
||||||
|
import vt
|
||||||
|
import pymisp
|
||||||
|
|
||||||
|
|
||||||
|
MISP_ATTRIBUTES = {
|
||||||
|
'input': [
|
||||||
|
'hostname',
|
||||||
|
'domain',
|
||||||
|
'ip-src',
|
||||||
|
'ip-dst',
|
||||||
|
'md5',
|
||||||
|
'sha1',
|
||||||
|
'sha256',
|
||||||
|
'url',
|
||||||
|
],
|
||||||
|
'format': 'misp_standard',
|
||||||
|
}
|
||||||
|
|
||||||
|
MODULE_INFO = {
|
||||||
|
'version': '1',
|
||||||
|
'author': 'Google Threat Intelligence team',
|
||||||
|
'description': ('An expansion module to have the observable\'s threat'
|
||||||
|
' score assessed by Google Threat Intelligence.'),
|
||||||
|
'module-type': ['expansion'],
|
||||||
|
'config': [
|
||||||
|
'apikey',
|
||||||
|
'event_limit',
|
||||||
|
'proxy_host',
|
||||||
|
'proxy_port',
|
||||||
|
'proxy_username',
|
||||||
|
'proxy_password'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_RESULTS_LIMIT = 10
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleThreatIntelligenceParser:
|
||||||
|
"""Main parser class to create the MISP event."""
|
||||||
|
def __init__(self, client: vt.Client, limit: int) -> None:
|
||||||
|
self.client = client
|
||||||
|
self.limit = limit or DEFAULT_RESULTS_LIMIT
|
||||||
|
self.misp_event = pymisp.MISPEvent()
|
||||||
|
self.attribute = pymisp.MISPAttribute()
|
||||||
|
self.parsed_objects = {}
|
||||||
|
self.input_types_mapping = {
|
||||||
|
'ip-src': self.parse_ip,
|
||||||
|
'ip-dst': self.parse_ip,
|
||||||
|
'domain': self.parse_domain,
|
||||||
|
'hostname': self.parse_domain,
|
||||||
|
'md5': self.parse_hash,
|
||||||
|
'sha1': self.parse_hash,
|
||||||
|
'sha256': self.parse_hash,
|
||||||
|
'url': self.parse_url
|
||||||
|
}
|
||||||
|
self.proxies = None
|
||||||
|
|
||||||
|
def query_api(self, attribute: dict) -> None:
|
||||||
|
"""Get data from the API and parse it."""
|
||||||
|
self.attribute.from_dict(**attribute)
|
||||||
|
self.input_types_mapping[self.attribute.type](self.attribute.value)
|
||||||
|
|
||||||
|
def get_results(self) -> dict:
|
||||||
|
"""Serialize the MISP event."""
|
||||||
|
event = self.misp_event.to_dict()
|
||||||
|
results = {
|
||||||
|
key: event[key] for key in ('Attribute', 'Object') \
|
||||||
|
if (key in event and event[key])
|
||||||
|
}
|
||||||
|
return {'results': results}
|
||||||
|
|
||||||
|
def create_gti_report_object(self, report):
|
||||||
|
"""Create GTI report object."""
|
||||||
|
report = report.to_dict()
|
||||||
|
permalink = ('https://www.virustotal.com/gui/'
|
||||||
|
f"{report['type']}/{report['id']}")
|
||||||
|
report_object = pymisp.MISPObject('Google-Threat-Intel-report')
|
||||||
|
report_object.add_attribute('permalink', type='link', value=permalink)
|
||||||
|
report_object.add_attribute(
|
||||||
|
'Threat Score', type='text',
|
||||||
|
value=get_key(
|
||||||
|
report, 'attributes.gti_assessment.threat_score.value'))
|
||||||
|
report_object.add_attribute(
|
||||||
|
'Verdict', type='text',
|
||||||
|
value=get_key(
|
||||||
|
report, 'attributes.gti_assessment.verdict.value').replace(
|
||||||
|
'VERDICT_', ''))
|
||||||
|
report_object.add_attribute(
|
||||||
|
'Severity', type='text',
|
||||||
|
value=get_key(
|
||||||
|
report, 'attributes.gti_assessment.severity.value').replace(
|
||||||
|
'SEVERITY_', ''))
|
||||||
|
report_object.add_attribute(
|
||||||
|
'Threat Label', type='text',
|
||||||
|
value=get_key(
|
||||||
|
report, ('attributes.popular_threat_classification'
|
||||||
|
'.suggested_threat_label')))
|
||||||
|
self.misp_event.add_object(**report_object)
|
||||||
|
return report_object.uuid
|
||||||
|
|
||||||
|
def parse_domain(self, domain: str) -> str:
|
||||||
|
"""Create domain MISP object."""
|
||||||
|
domain_report = self.client.get_object(f'/domains/{domain}')
|
||||||
|
|
||||||
|
# DOMAIN
|
||||||
|
domain_object = pymisp.MISPObject('domain-ip')
|
||||||
|
domain_object.add_attribute(
|
||||||
|
'domain', type='domain', value=domain_report.id)
|
||||||
|
|
||||||
|
report_uuid = self.create_gti_report_object(domain_report)
|
||||||
|
domain_object.add_reference(report_uuid, 'analyzed-with')
|
||||||
|
self.misp_event.add_object(**domain_object)
|
||||||
|
return domain_object.uuid
|
||||||
|
|
||||||
|
def parse_hash(self, file_hash: str) -> str:
|
||||||
|
"""Create hash MISP object."""
|
||||||
|
file_report = self.client.get_object(f'/files/{file_hash}')
|
||||||
|
file_object = pymisp.MISPObject('file')
|
||||||
|
for hash_type in ('md5', 'sha1', 'sha256'):
|
||||||
|
file_object.add_attribute(
|
||||||
|
hash_type,
|
||||||
|
**{'type': hash_type, 'value': file_report.get(hash_type)})
|
||||||
|
|
||||||
|
report_uuid = self.create_gti_report_object(file_report)
|
||||||
|
file_object.add_reference(report_uuid, 'analyzed-with')
|
||||||
|
self.misp_event.add_object(**file_object)
|
||||||
|
return file_object.uuid
|
||||||
|
|
||||||
|
def parse_ip(self, ip: str) -> str:
|
||||||
|
"""Create ip MISP object."""
|
||||||
|
ip_report = self.client.get_object(f'/ip_addresses/{ip}')
|
||||||
|
|
||||||
|
# IP
|
||||||
|
ip_object = pymisp.MISPObject('domain-ip')
|
||||||
|
ip_object.add_attribute('ip', type='ip-dst', value=ip_report.id)
|
||||||
|
|
||||||
|
report_uuid = self.create_gti_report_object(ip_report)
|
||||||
|
ip_object.add_reference(report_uuid, 'analyzed-with')
|
||||||
|
self.misp_event.add_object(**ip_object)
|
||||||
|
return ip_object.uuid
|
||||||
|
|
||||||
|
def parse_url(self, url: str) -> str:
|
||||||
|
"""Create URL MISP object."""
|
||||||
|
url_id = vt.url_id(url)
|
||||||
|
url_report = self.client.get_object(f'/urls/{url_id}')
|
||||||
|
|
||||||
|
url_object = pymisp.MISPObject('url')
|
||||||
|
url_object.add_attribute('url', type='url', value=url_report.url)
|
||||||
|
|
||||||
|
report_uuid = self.create_gti_report_object(url_report)
|
||||||
|
url_object.add_reference(report_uuid, 'analyzed-with')
|
||||||
|
self.misp_event.add_object(**url_object)
|
||||||
|
return url_object.uuid
|
||||||
|
|
||||||
|
|
||||||
|
def get_key(dictionary, key, default_value=''):
|
||||||
|
"""Get value from nested dictionaries."""
|
||||||
|
dictionary = dictionary or {}
|
||||||
|
keys = key.split('.')
|
||||||
|
field_name = keys.pop()
|
||||||
|
for k in keys:
|
||||||
|
if k not in dictionary:
|
||||||
|
return default_value
|
||||||
|
dictionary = dictionary[k]
|
||||||
|
return dictionary.get(field_name, default_value)
|
||||||
|
|
||||||
|
|
||||||
|
def get_proxy_settings(config: dict) -> dict:
|
||||||
|
"""Returns proxy settings in the requests format or None if not set up."""
|
||||||
|
proxies = None
|
||||||
|
host = config.get('proxy_host')
|
||||||
|
port = config.get('proxy_port')
|
||||||
|
username = config.get('proxy_username')
|
||||||
|
password = config.get('proxy_password')
|
||||||
|
|
||||||
|
if host:
|
||||||
|
if not port:
|
||||||
|
raise KeyError(
|
||||||
|
('The google_threat_intelligence_proxy_host config is set, '
|
||||||
|
'please also set the virustotal_proxy_port.'))
|
||||||
|
parsed = parse.urlparse(host)
|
||||||
|
if 'http' in parsed.scheme:
|
||||||
|
scheme = 'http'
|
||||||
|
else:
|
||||||
|
scheme = parsed.scheme
|
||||||
|
netloc = parsed.netloc
|
||||||
|
host = f'{netloc}:{port}'
|
||||||
|
|
||||||
|
if username:
|
||||||
|
if not password:
|
||||||
|
raise KeyError(('The google_threat_intelligence_'
|
||||||
|
' proxy_host config is set, please also'
|
||||||
|
' set the virustotal_proxy_password.'))
|
||||||
|
auth = f'{username}:{password}'
|
||||||
|
host = auth + '@' + host
|
||||||
|
|
||||||
|
proxies = {
|
||||||
|
'http': f'{scheme}://{host}',
|
||||||
|
'https': f'{scheme}://{host}'
|
||||||
|
}
|
||||||
|
return proxies
|
||||||
|
|
||||||
|
|
||||||
|
def dict_handler(request: dict):
|
||||||
|
"""MISP entry point fo the module."""
|
||||||
|
if not request.get('config') or not request['config'].get('apikey'):
|
||||||
|
return {
|
||||||
|
'error': ('A Google Threat Intelligence api '
|
||||||
|
'key is required for this module.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if not request.get('attribute'):
|
||||||
|
return {
|
||||||
|
'error': ('This module requires an "attribute" field as input,'
|
||||||
|
' which should contain at least a type, a value and an'
|
||||||
|
' uuid.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if request['attribute']['type'] not in MISP_ATTRIBUTES['input']:
|
||||||
|
return {'error': 'Unsupported attribute type.'}
|
||||||
|
|
||||||
|
event_limit = request['config'].get('event_limit')
|
||||||
|
attribute = request['attribute']
|
||||||
|
|
||||||
|
try:
|
||||||
|
proxy_settings = get_proxy_settings(request.get('config'))
|
||||||
|
client = vt.Client(
|
||||||
|
request['config']['apikey'],
|
||||||
|
headers={
|
||||||
|
'x-tool': 'MISPModuleGTIExpansion',
|
||||||
|
},
|
||||||
|
proxy=proxy_settings['http'] if proxy_settings else None)
|
||||||
|
parser = GoogleThreatIntelligenceParser(
|
||||||
|
client, int(event_limit) if event_limit else None)
|
||||||
|
parser.query_api(attribute)
|
||||||
|
except vt.APIError as ex:
|
||||||
|
return {'error': ex.message}
|
||||||
|
except KeyError as ex:
|
||||||
|
return {'error': str(ex)}
|
||||||
|
|
||||||
|
return parser.get_results()
|
||||||
|
|
||||||
|
|
||||||
|
def introspection():
|
||||||
|
"""Returns the module input attributes required."""
|
||||||
|
return MISP_ATTRIBUTES
|
||||||
|
|
||||||
|
|
||||||
|
def version():
|
||||||
|
"""Returns the module metadata."""
|
||||||
|
return MODULE_INFO
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Testing/debug calls.
|
||||||
|
import os
|
||||||
|
api_key = os.getenv('GTI_API_KEY')
|
||||||
|
# File
|
||||||
|
request_data = {
|
||||||
|
'config': {'apikey': api_key},
|
||||||
|
'attribute': {
|
||||||
|
'type': 'sha256',
|
||||||
|
'value': ('ed01ebfbc9eb5bbea545af4d01bf5f10'
|
||||||
|
'71661840480439c6e5babe8e080e41aa')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = dict_handler(request_data)
|
||||||
|
report_obj = response['results']['Object'][0]
|
||||||
|
print(report_obj.to_dict())
|
||||||
|
|
||||||
|
# URL
|
||||||
|
request_data = {
|
||||||
|
'config': {'apikey': api_key},
|
||||||
|
'attribute': {
|
||||||
|
'type': 'url',
|
||||||
|
'value': 'http://47.21.48.182:60813/Mozi.a'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = dict_handler(request_data)
|
||||||
|
report_obj = response['results']['Object'][0]
|
||||||
|
print(report_obj.to_dict())
|
||||||
|
|
||||||
|
# Ip
|
||||||
|
request_data = {
|
||||||
|
'config': {'apikey': api_key},
|
||||||
|
'attribute': {
|
||||||
|
'type': 'ip-src',
|
||||||
|
'value': '180.72.148.38'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = dict_handler(request_data)
|
||||||
|
report_obj = response['results']['Object'][0]
|
||||||
|
print(report_obj.to_dict())
|
||||||
|
|
||||||
|
# Domain
|
||||||
|
request_data = {
|
||||||
|
'config': {'apikey': api_key},
|
||||||
|
'attribute': {
|
||||||
|
'type': 'domain',
|
||||||
|
'value': 'qexyhuv.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = dict_handler(request_data)
|
||||||
|
report_obj = response['results']['Object'][0]
|
||||||
|
print(report_obj.to_dict())
|
|
@ -19,6 +19,8 @@ git submodule init && git submodule update ## Initialize misp-objects submodul
|
||||||
python3 app.py -i ## Initialize db
|
python3 app.py -i ## Initialize db
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Don't forget to install **misp-modules**...
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
Edit `config.py`
|
Edit `config.py`
|
||||||
|
@ -35,8 +37,6 @@ Edit `config.py`
|
||||||
|
|
||||||
- `ADMIN_PASSWORD`: Password for Admin user if `ADMIN_USER` is True
|
- `ADMIN_PASSWORD`: Password for Admin user if `ADMIN_USER` is True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Rename `config.cfg.sample` to `config.cfg` then edit it:
|
Rename `config.cfg.sample` to `config.cfg` then edit it:
|
||||||
|
|
||||||
- `ADMIN_USER`: If True, config page will not be accessible
|
- `ADMIN_USER`: If True, config page will not be accessible
|
||||||
|
|
|
@ -37,6 +37,7 @@ def create_app():
|
||||||
app.register_blueprint(home_blueprint, url_prefix="/")
|
app.register_blueprint(home_blueprint, url_prefix="/")
|
||||||
app.register_blueprint(history_blueprint, url_prefix="/")
|
app.register_blueprint(history_blueprint, url_prefix="/")
|
||||||
app.register_blueprint(account_blueprint, url_prefix="/")
|
app.register_blueprint(account_blueprint, url_prefix="/")
|
||||||
|
csrf.exempt(home_blueprint)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Session_db(db.Model):
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"uuid": self.uuid,
|
"uuid": self.uuid,
|
||||||
"modules": json.loads(self.modules_list),
|
"modules": json.loads(self.modules_list),
|
||||||
"query_enter": self.query_enter,
|
"query_enter": json.loads(self.query_enter),
|
||||||
"input_query": self.input_query,
|
"input_query": self.input_query,
|
||||||
"config_module": json.loads(self.config_module),
|
"config_module": json.loads(self.config_module),
|
||||||
"result": json.loads(self.result),
|
"result": json.loads(self.result),
|
||||||
|
@ -51,7 +51,7 @@ class Session_db(db.Model):
|
||||||
json_dict = {
|
json_dict = {
|
||||||
"uuid": self.uuid,
|
"uuid": self.uuid,
|
||||||
"modules": json.loads(self.modules_list),
|
"modules": json.loads(self.modules_list),
|
||||||
"query": self.query_enter,
|
"query": json.loads(self.query_enter),
|
||||||
"input": self.input_query,
|
"input": self.input_query,
|
||||||
"query_date": self.query_date.strftime('%Y-%m-%d %H:%M')
|
"query_date": self.query_date.strftime('%Y-%m-%d %H:%M')
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,8 +146,8 @@ def util_remove_node_session(node_uuid, parent, parent_path):
|
||||||
child = parent["children"][i]
|
child = parent["children"][i]
|
||||||
if child["uuid"] == node_uuid:
|
if child["uuid"] == node_uuid:
|
||||||
del parent_path["children"][i]
|
del parent_path["children"][i]
|
||||||
return
|
return True
|
||||||
elif child["children"]:
|
elif "children" in child and child["children"]:
|
||||||
return util_remove_node_session(node_uuid, child, parent_path["children"][i])
|
return util_remove_node_session(node_uuid, child, parent_path["children"][i])
|
||||||
|
|
||||||
def remove_node_session(node_uuid):
|
def remove_node_session(node_uuid):
|
||||||
|
@ -160,7 +160,9 @@ def remove_node_session(node_uuid):
|
||||||
loc = i
|
loc = i
|
||||||
break
|
break
|
||||||
elif q_value["children"]:
|
elif q_value["children"]:
|
||||||
return util_remove_node_session(node_uuid, q_value, sess[keys_list[i]])
|
if util_remove_node_session(node_uuid, q_value, sess[keys_list[i]]):
|
||||||
|
loc = i
|
||||||
|
break
|
||||||
if loc:
|
if loc:
|
||||||
del sess[keys_list[i]]
|
del sess[keys_list[i]]
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
import ast
|
||||||
import json
|
import json
|
||||||
from flask import Blueprint, render_template, request, jsonify, session as sess
|
from flask import Blueprint, redirect, render_template, request, jsonify, session as sess
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from . import session_class as SessionModel
|
from . import session_class as SessionModel
|
||||||
from . import home_core as HomeModel
|
from . import home_core as HomeModel
|
||||||
from .utils.utils import admin_user_active
|
from .utils.utils import admin_user_active, FLOWINTEL_URL
|
||||||
|
|
||||||
home_blueprint = Blueprint(
|
home_blueprint = Blueprint(
|
||||||
'home',
|
'home',
|
||||||
|
@ -13,18 +14,35 @@ home_blueprint = Blueprint(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@home_blueprint.route("/")
|
@home_blueprint.route("/", methods=["GET", "POST"])
|
||||||
def home():
|
def home():
|
||||||
|
try:
|
||||||
|
del sess["query"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
sess["admin_user"] = bool(admin_user_active())
|
sess["admin_user"] = bool(admin_user_active())
|
||||||
if "query" in request.args:
|
if "query" in request.args:
|
||||||
return render_template("home.html", query=request.args.get("query"))
|
sess["query"] = ast.literal_eval(request.args.get("query"))
|
||||||
|
if "query" in request.form:
|
||||||
|
sess["query"] = json.loads(request.form.get("query"))
|
||||||
return render_template("home.html")
|
return render_template("home.html")
|
||||||
|
|
||||||
|
@home_blueprint.route("/get_query", methods=['GET', 'POST'])
|
||||||
|
def get_query():
|
||||||
|
"""Get result from flowintel"""
|
||||||
|
if "query" in sess:
|
||||||
|
return {"query": sess.get("query")}
|
||||||
|
return {"message": "No query"}
|
||||||
|
|
||||||
@home_blueprint.route("/home/<sid>", methods=["GET", "POST"])
|
@home_blueprint.route("/home/<sid>", methods=["GET", "POST"])
|
||||||
def home_query(sid):
|
def home_query(sid):
|
||||||
|
try:
|
||||||
|
del sess["query"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
sess["admin_user"] = admin_user_active()
|
sess["admin_user"] = admin_user_active()
|
||||||
if "query" in request.args:
|
if "query" in request.args:
|
||||||
query = request.args.get("query")
|
sess["query"] = [request.args.get("query")]
|
||||||
return render_template("home.html", query=query, sid=sid)
|
return render_template("home.html", query=query, sid=sid)
|
||||||
return render_template("404.html")
|
return render_template("404.html")
|
||||||
|
|
||||||
|
@ -33,21 +51,28 @@ def query(sid):
|
||||||
sess["admin_user"] = admin_user_active()
|
sess["admin_user"] = admin_user_active()
|
||||||
session = HomeModel.get_session(sid)
|
session = HomeModel.get_session(sid)
|
||||||
flag=False
|
flag=False
|
||||||
|
modules_list = []
|
||||||
if session:
|
if session:
|
||||||
flag = True
|
flag = True
|
||||||
query_loc = session.query_enter
|
query_loc = json.loads(session.query_enter)
|
||||||
|
modules_list = json.loads(session.modules_list)
|
||||||
else:
|
else:
|
||||||
for s in SessionModel.sessions:
|
for s in SessionModel.sessions:
|
||||||
if s.uuid == sid:
|
if s.uuid == sid:
|
||||||
flag = True
|
flag = True
|
||||||
query_loc = s.query
|
query_loc = s.query
|
||||||
session=s
|
session=s
|
||||||
|
modules_list = session.modules_list
|
||||||
|
query_str = ", ".join(query_loc)
|
||||||
|
if len(query_str) > 40:
|
||||||
|
query_str = query_str[0:40] + "..."
|
||||||
if flag:
|
if flag:
|
||||||
return render_template("query.html",
|
return render_template("query.html",
|
||||||
query=query_loc,
|
query=query_loc,
|
||||||
|
query_str=query_str,
|
||||||
sid=sid,
|
sid=sid,
|
||||||
input_query=session.input_query,
|
input_query=session.input_query,
|
||||||
modules=json.loads(session.modules_list),
|
modules=modules_list,
|
||||||
query_date=session.query_date.strftime('%Y-%m-%d %H:%M'))
|
query_date=session.query_date.strftime('%Y-%m-%d %H:%M'))
|
||||||
return render_template("404.html")
|
return render_template("404.html")
|
||||||
|
|
||||||
|
@ -60,18 +85,20 @@ def get_query_info(sid):
|
||||||
flag=False
|
flag=False
|
||||||
if session:
|
if session:
|
||||||
flag = True
|
flag = True
|
||||||
query_loc = session.query_enter
|
query_loc = json.loads(session.query_enter)
|
||||||
|
modules_list = json.loads(session.modules_list)
|
||||||
else:
|
else:
|
||||||
for s in SessionModel.sessions:
|
for s in SessionModel.sessions:
|
||||||
if s.uuid == sid:
|
if s.uuid == sid:
|
||||||
flag = True
|
flag = True
|
||||||
query_loc = s.query
|
query_loc = s.query
|
||||||
|
modules_list = s.modules_list
|
||||||
session=s
|
session=s
|
||||||
if flag:
|
if flag:
|
||||||
loc_dict = {
|
loc_dict = {
|
||||||
"query": query_loc,
|
"query": query_loc,
|
||||||
"input_query": session.input_query,
|
"input_query": session.input_query,
|
||||||
"modules": json.loads(session.modules_list),
|
"modules": modules_list,
|
||||||
"query_date": session.query_date.strftime('%Y-%m-%d %H:%M')
|
"query_date": session.query_date.strftime('%Y-%m-%d %H:%M')
|
||||||
}
|
}
|
||||||
return loc_dict
|
return loc_dict
|
||||||
|
@ -227,3 +254,9 @@ def change_status():
|
||||||
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
|
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
|
||||||
return {'message': 'Need to pass "module_id"', 'toast_class': "warning-subtle"}, 400
|
return {'message': 'Need to pass "module_id"', 'toast_class': "warning-subtle"}, 400
|
||||||
return {'message': 'Permission denied', 'toast_class': "danger-subtle"}, 403
|
return {'message': 'Permission denied', 'toast_class': "danger-subtle"}, 403
|
||||||
|
|
||||||
|
|
||||||
|
@home_blueprint.route("/flowintel_url")
|
||||||
|
def flowintel_url():
|
||||||
|
"""send result to flowintel-cm"""
|
||||||
|
return {"url": f"{FLOWINTEL_URL}/analyzer/recieve_result"}, 200
|
||||||
|
|
|
@ -163,7 +163,7 @@ def create_new_session_tree(current_session, parent_id):
|
||||||
loc_json = {
|
loc_json = {
|
||||||
"uuid": loc_session.uuid,
|
"uuid": loc_session.uuid,
|
||||||
"modules": json.loads(loc_session.modules_list),
|
"modules": json.loads(loc_session.modules_list),
|
||||||
"query": loc_session.query_enter,
|
"query": json.loads(loc_session.query_enter),
|
||||||
"input": loc_session.input_query,
|
"input": loc_session.input_query,
|
||||||
"query_date": loc_session.query_date.strftime('%Y-%m-%d %H:%M'),
|
"query_date": loc_session.query_date.strftime('%Y-%m-%d %H:%M'),
|
||||||
"config": json.loads(loc_session.config_module),
|
"config": json.loads(loc_session.config_module),
|
||||||
|
|
|
@ -64,9 +64,12 @@ class Session_class:
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start all worker"""
|
"""Start all worker"""
|
||||||
for i in range(len(self.modules_list)):
|
cp = 0
|
||||||
|
for i in self.query:
|
||||||
|
for j in self.modules_list:
|
||||||
|
self.jobs.put((cp, i, j))
|
||||||
|
cp += 1
|
||||||
#need the index and the url in each queue item.
|
#need the index and the url in each queue item.
|
||||||
self.jobs.put((i, self.modules_list[i]))
|
|
||||||
for _ in range(self.thread_count):
|
for _ in range(self.thread_count):
|
||||||
worker = Thread(target=self.process)
|
worker = Thread(target=self.process)
|
||||||
worker.daemon = True
|
worker.daemon = True
|
||||||
|
@ -111,28 +114,30 @@ class Session_class:
|
||||||
|
|
||||||
modules = query_get_module()
|
modules = query_get_module()
|
||||||
loc_query = {}
|
loc_query = {}
|
||||||
|
self.result[work[1]] = dict()
|
||||||
# If Misp format
|
# If Misp format
|
||||||
for module in modules:
|
for module in modules:
|
||||||
if module["name"] == work[1]:
|
if module["name"] == work[2]:
|
||||||
if "format" in module["mispattributes"]:
|
if "format" in module["mispattributes"]:
|
||||||
loc_query = {
|
loc_query = {
|
||||||
"type": self.input_query,
|
"type": self.input_query,
|
||||||
"value": self.query,
|
"value": work[1],
|
||||||
"uuid": str(uuid.uuid4())
|
"uuid": str(uuid.uuid4())
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
loc_config = {}
|
loc_config = {}
|
||||||
if work[1] in self.config_module:
|
if work[2] in self.config_module:
|
||||||
loc_config = self.config_module[work[1]]
|
loc_config = self.config_module[work[2]]
|
||||||
|
|
||||||
if loc_query:
|
if loc_query:
|
||||||
send_to = {"module": work[1], "attribute": loc_query, "config": loc_config}
|
send_to = {"module": work[2], "attribute": loc_query, "config": loc_config}
|
||||||
else:
|
else:
|
||||||
send_to = {"module": work[1], self.input_query: self.query, "config": loc_config}
|
send_to = {"module": work[2], self.input_query: work[1], "config": loc_config}
|
||||||
res = query_post_query(send_to)
|
res = query_post_query(send_to)
|
||||||
|
|
||||||
## Sort attr in object by ui-priority
|
## Sort attr in object by ui-priority
|
||||||
|
if res:
|
||||||
if "results" in res:
|
if "results" in res:
|
||||||
if "Object" in res["results"]:
|
if "Object" in res["results"]:
|
||||||
for obj in res["results"]["Object"]:
|
for obj in res["results"]["Object"]:
|
||||||
|
@ -144,11 +149,9 @@ class Session_class:
|
||||||
# After adding 'ui-priority'
|
# After adding 'ui-priority'
|
||||||
obj["Attribute"].sort(key=lambda x: x["ui-priority"], reverse=True)
|
obj["Attribute"].sort(key=lambda x: x["ui-priority"], reverse=True)
|
||||||
|
|
||||||
|
if res and "error" in res:
|
||||||
# print(res)
|
|
||||||
if "error" in res:
|
|
||||||
self.nb_errors += 1
|
self.nb_errors += 1
|
||||||
self.result[work[1]] = res
|
self.result[work[1]][work[2]] = res
|
||||||
|
|
||||||
self.jobs.task_done()
|
self.jobs.task_done()
|
||||||
return True
|
return True
|
||||||
|
@ -161,7 +164,7 @@ class Session_class:
|
||||||
s = Session_db(
|
s = Session_db(
|
||||||
uuid=str(self.uuid),
|
uuid=str(self.uuid),
|
||||||
modules_list=json.dumps(self.modules_list),
|
modules_list=json.dumps(self.modules_list),
|
||||||
query_enter=self.query,
|
query_enter=json.dumps(self.query),
|
||||||
input_query=self.input_query,
|
input_query=self.input_query,
|
||||||
config_module=json.dumps(self.config_module),
|
config_module=json.dumps(self.config_module),
|
||||||
result=json.dumps(self.result),
|
result=json.dumps(self.result),
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
template: `
|
template: `
|
||||||
<li><a :href="'/query/'+history.uuid" :title="'Attribute: \\n' +history.input+ '\\n\\nModules: \\n' + history.modules">[[history.query]]</a></li>
|
<li v-if="history.query"><a :href="'/query/'+history.uuid" :title="'Attribute: \\n' +history.input+ '\\n\\nModules: \\n' + history.modules">[[history.query.join(", ")]]</a></li>
|
||||||
<ul>
|
<ul>
|
||||||
<template v-for="child in history.children">
|
<template v-for="child in history.children">
|
||||||
<history_view :history="child"></history_view>
|
<history_view :history="child"></history_view>
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default {
|
||||||
|
|
||||||
<ul class="list-group list-group-horizontal" style="padding-top: 5px;">
|
<ul class="list-group list-group-horizontal" style="padding-top: 5px;">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<h5>[[history.query]]</h5>
|
<h5>[[history.query.join(", ")]]</h5>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<h5 style="color: brown"><u>Input Attributes</u></h5>
|
<h5 style="color: brown"><u>Input Attributes</u></h5>
|
||||||
|
@ -69,7 +69,7 @@ export default {
|
||||||
<div class="collapse" :id="'collapse'+history.uuid" style="width: 70%; margin-left: 30px">
|
<div class="collapse" :id="'collapse'+history.uuid" style="width: 70%; margin-left: 30px">
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">[[history.query]]</h5>
|
<h5 class="mb-1">[[history.query.join(", ")]]</h5>
|
||||||
<small><i>[[history.uuid]]</i></small>
|
<small><i>[[history.uuid]]</i></small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>
|
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>
|
||||||
|
|
|
@ -10,6 +10,7 @@ function parseMispObject(misp_object, query_url, functionToCall){
|
||||||
if(query_url){
|
if(query_url){
|
||||||
$query=$("<a>").attr("href", query_url+v.value).text("query").css("margin-left", "10px")
|
$query=$("<a>").attr("href", query_url+v.value).text("query").css("margin-left", "10px")
|
||||||
}
|
}
|
||||||
|
if(functionToCall){
|
||||||
// `_${functionToCall.name}('${v.value}')` refer to 'window._query_as_same = query_as_same' in my vue file
|
// `_${functionToCall.name}('${v.value}')` refer to 'window._query_as_same = query_as_same' in my vue file
|
||||||
$query_same = $("<button>").attr({"onclick": `_${functionToCall.name}('${v.value}')`,
|
$query_same = $("<button>").attr({"onclick": `_${functionToCall.name}('${v.value}')`,
|
||||||
"title": "Query this value with the same attribute and modules as the main query",
|
"title": "Query this value with the same attribute and modules as the main query",
|
||||||
|
@ -18,6 +19,7 @@ function parseMispObject(misp_object, query_url, functionToCall){
|
||||||
.text("query as same")
|
.text("query as same")
|
||||||
.css({"margin-left": "10px", "padding": "0", "--bs-btn-border-width": "0"})
|
.css({"margin-left": "10px", "padding": "0", "--bs-btn-border-width": "0"})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$container.append(
|
$container.append(
|
||||||
$("<div>").css("margin-top", "10px").append(
|
$("<div>").css("margin-top", "10px").append(
|
||||||
|
@ -74,6 +76,7 @@ function parseMispAttr(misp_attr, misp_types, key, query_url, query_as_same){
|
||||||
$query=$("<a>").attr("href", query_url+misp_attr).text("query").css("margin-left", "10px")
|
$query=$("<a>").attr("href", query_url+misp_attr).text("query").css("margin-left", "10px")
|
||||||
}
|
}
|
||||||
// `_${functionToCall.name}('${misp_attr}')` refer to 'window._query_as_same = query_as_same' in my vue file
|
// `_${functionToCall.name}('${misp_attr}')` refer to 'window._query_as_same = query_as_same' in my vue file
|
||||||
|
if(query_as_same){
|
||||||
$query_same = $("<button>").attr({"onclick": `_${query_as_same.name}('${misp_attr}')`,
|
$query_same = $("<button>").attr({"onclick": `_${query_as_same.name}('${misp_attr}')`,
|
||||||
"title": "Query this value with the same attribute and modules as the main query",
|
"title": "Query this value with the same attribute and modules as the main query",
|
||||||
"class": "btn btn-link"
|
"class": "btn btn-link"
|
||||||
|
@ -81,6 +84,7 @@ function parseMispAttr(misp_attr, misp_types, key, query_url, query_as_same){
|
||||||
.text("query as same")
|
.text("query as same")
|
||||||
.css({"margin-left": "10px", "padding": "0", "--bs-btn-border-width": "0"})
|
.css({"margin-left": "10px", "padding": "0", "--bs-btn-border-width": "0"})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var $mainContainer = $('<div>')
|
var $mainContainer = $('<div>')
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
export default {
|
|
||||||
delimiters: ['[[', ']]'],
|
|
||||||
props: {
|
|
||||||
tasks_list: Object
|
|
||||||
},
|
|
||||||
emits: ['tasks_list', 'current_filter'],
|
|
||||||
setup(props, {emit}) {
|
|
||||||
let show_ongoing = true
|
|
||||||
let current_filter = ""
|
|
||||||
let asc_desc = true
|
|
||||||
|
|
||||||
async function filter_ongoing(ongoing){
|
|
||||||
show_ongoing = ongoing
|
|
||||||
|
|
||||||
if(show_ongoing){
|
|
||||||
const res = await fetch('/my_assignment/sort_by_ongoing?page=1')
|
|
||||||
let loc = await res.json()
|
|
||||||
emit('tasks_list', loc)
|
|
||||||
}else{
|
|
||||||
const res = await fetch('/my_assignment/sort_by_finished?page=1')
|
|
||||||
let loc = await res.json()
|
|
||||||
emit('tasks_list', loc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort_by_title(){
|
|
||||||
current_filter = "title"
|
|
||||||
asc_desc_filter()
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort_by_last_modif(){
|
|
||||||
current_filter = "last_modif"
|
|
||||||
asc_desc_filter()
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort_by_deadline(){
|
|
||||||
current_filter = "deadline"
|
|
||||||
asc_desc_filter()
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort_by_status(){
|
|
||||||
current_filter = "status_id"
|
|
||||||
asc_desc_filter()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function asc_desc_filter(change=false){
|
|
||||||
emit("current_filter", current_filter)
|
|
||||||
if(change)
|
|
||||||
asc_desc = !asc_desc
|
|
||||||
|
|
||||||
let res
|
|
||||||
if (current_filter){
|
|
||||||
if(show_ongoing)
|
|
||||||
res = await fetch('/my_assignment/tasks/ongoing?page=1&filter=' + current_filter)
|
|
||||||
else
|
|
||||||
res = await fetch('/my_assignment/tasks/finished?page=1&filter=' + current_filter)
|
|
||||||
let loc = await res.json()
|
|
||||||
if(asc_desc)
|
|
||||||
emit('tasks_list', loc)
|
|
||||||
else
|
|
||||||
loc["tasks"] = loc["tasks"].reverse()
|
|
||||||
emit('tasks_list', loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
filter_ongoing,
|
|
||||||
sort_by_last_modif,
|
|
||||||
sort_by_title,
|
|
||||||
sort_by_deadline,
|
|
||||||
sort_by_status,
|
|
||||||
asc_desc_filter
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapsefiltertask" aria-expanded="false" aria-controls="collapsefiltertask">
|
|
||||||
filter
|
|
||||||
</button>
|
|
||||||
<div class="collapse" id="collapsefiltertask">
|
|
||||||
<div class="card card-body">
|
|
||||||
<div class="d-flex w-100 justify-content-between">
|
|
||||||
<div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="radioStatus" id="radioStatusOngoing" @click="filter_ongoing(true)" checked>
|
|
||||||
<label class="form-check-label" for="radioStatusOngoing">Ongoing Tasks</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="radioStatus" id="radioStatusFinished" @click="filter_ongoing(false)">
|
|
||||||
<label class="form-check-label" for="radioStatusFinished">Finished Tasks</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="radioOther" id="radioOtherTitle" @click="sort_by_title()">
|
|
||||||
<label class="form-check-label" for="radioOtherTitle">Title</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="radioOther" id="radioOrderAsc" @click="sort_by_last_modif()">
|
|
||||||
<label class="form-check-label" for="radioOrderAsc">Last modification</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="radioOther" id="radioOtherDeadLine" @click="sort_by_deadline()">
|
|
||||||
<label class="form-check-label" for="radioOtherDeadLine">Deadline</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="radio" name="radioOther" id="radioOtherStatus" @click="sort_by_status()">
|
|
||||||
<label class="form-check-label" for="radioOtherStatus">Status</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:flex">
|
|
||||||
<span style="margin-right: 10px">Asc</span>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="flexSwitchCheckDefault" @click="asc_desc_filter(true)">
|
|
||||||
<label class="form-check-label" for="flexSwitchCheckDefault">Desc</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
}
|
|
|
@ -30,7 +30,7 @@
|
||||||
<div class="list-group" style="margin-bottom: 20px;">
|
<div class="list-group" style="margin-bottom: 20px;">
|
||||||
<a :href="'/query/'+h.uuid" class="list-group-item list-group-item-action">
|
<a :href="'/query/'+h.uuid" class="list-group-item list-group-item-action">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">[[h.query]]</h5>
|
<h5 class="mb-1">[[h.query.join(", ")]]</h5>
|
||||||
<small><i>[[h.uuid]]</i></small>
|
<small><i>[[h.uuid]]</i></small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>
|
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<a style="text-decoration: none; color: black;" data-bs-toggle="collapse" :href="'#collapse'+his.uuid" role="button" aria-expanded="false" :aria-controls="'collapse'+his.uuid">
|
<a style="text-decoration: none; color: black;" data-bs-toggle="collapse" :href="'#collapse'+his.uuid" role="button" aria-expanded="false" :aria-controls="'collapse'+his.uuid">
|
||||||
<ul class="list-group list-group-horizontal">
|
<ul class="list-group list-group-horizontal">
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<h4>[[his.query]]</h4>
|
<h4>[[his.query.join(", ")]]</h4>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<h5 style="color: brown"><u>Input Attributes</u></h5>
|
<h5 style="color: brown"><u>Input Attributes</u></h5>
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
<div class="collapse" :id="'collapse'+his.uuid" style="width: 70%; margin-left:30px">
|
<div class="collapse" :id="'collapse'+his.uuid" style="width: 70%; margin-left:30px">
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">[[his.query]]</h5>
|
<h5 class="mb-1">[[his.query.join(", ")]]</h5>
|
||||||
<small><i>[[his.uuid]]</i></small>
|
<small><i>[[his.uuid]]</i></small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>
|
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>
|
||||||
|
|
|
@ -19,10 +19,15 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-11">
|
<div class="col-11">
|
||||||
<div style="width:50%; transform: translate(50%, 0);">
|
<div style="width:50%; transform: translate(50%, 0);">
|
||||||
<div>
|
<template v-for="elem in cp_entries">
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
<input type="hidden" id="parent_id" value="{{sid}}">
|
<input type="hidden" id="parent_id" value="{{sid}}">
|
||||||
<input type="text" value="{{query}}" id="process-query" placeholder="Enter here..." autofocus class="form-control" style="border-radius: 5px;" />
|
<input type="text" v-if="queries" :value="queries[elem-1]" :id="'process-query-'+elem" placeholder="Enter here..." autofocus class="form-control" style="border-radius: 5px;" />
|
||||||
|
<input type="text" v-else :id="'process-query-'+elem" placeholder="Enter here..." autofocus class="form-control" style="border-radius: 5px;" />
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<button @click="add_entry()" class="btn btn-secondary" style="margin-top: 10px;">Add new entry</button>
|
||||||
|
<button @click="delete_entry()" class="btn btn-danger" style="margin-top: 10px;">Delete entry</button>
|
||||||
<span v-if="status_site" style="color: brown;" id="status">[[status_site]]</span>
|
<span v-if="status_site" style="color: brown;" id="status">[[status_site]]</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -54,8 +59,13 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Display in case a module as reauest_on_query activate -->
|
<div title="Configure modules even if a configuration as already been given" style="margin-top: 10px; transform: translate(25%, 0);">
|
||||||
<div v-if="config_query.length" style="margin-top: 10px; padding: 5px" class="row">
|
<input type="checkbox" id="own_config" name="own_config" @click="own_config()" style="margin-right: 5px;">
|
||||||
|
<label for="own_config">Configure all modules </label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Display in case a module as request_on_query activate -->
|
||||||
|
<div v-if="config_query.length" style="margin-top: 10px; padding: 6px" class="row">
|
||||||
<h4>Config</h4>
|
<h4>Config</h4>
|
||||||
<div class="card col-4" style="margin-top: 10px; padding: 15px" v-for="module in config_query">
|
<div class="card col-4" style="margin-top: 10px; padding: 15px" v-for="module in config_query">
|
||||||
<h4>[[module.name]]</h4>
|
<h4>[[module.name]]</h4>
|
||||||
|
@ -86,14 +96,37 @@
|
||||||
|
|
||||||
const progress = ref(0)
|
const progress = ref(0)
|
||||||
|
|
||||||
const current_query = ref()
|
const current_query = ref([])
|
||||||
const status_site = ref()
|
const status_site = ref()
|
||||||
|
|
||||||
const config_query = ref([])
|
const config_query = ref([])
|
||||||
|
|
||||||
|
const cp_entries = ref(1)
|
||||||
|
const queries = ref()
|
||||||
|
|
||||||
|
let own_config_bool = false
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchQuery(){
|
||||||
|
const res = await fetch('/get_query')
|
||||||
|
let loc = await res.json()
|
||||||
|
if("message" in loc){
|
||||||
|
queries.value = false
|
||||||
|
}else{
|
||||||
|
queries.value = loc["query"]
|
||||||
|
cp_entries.value = queries.value.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchQuery()
|
||||||
|
|
||||||
|
|
||||||
async function actionQuery(){
|
async function actionQuery(){
|
||||||
current_query.value = $("#process-query").val()
|
current_query.value = []
|
||||||
|
for(let i=1;i<=cp_entries.value;i++){
|
||||||
|
let loc_query_res = $("#process-query-"+i).val()
|
||||||
|
if(loc_query_res)
|
||||||
|
current_query.value.push(loc_query_res)
|
||||||
|
}
|
||||||
if (!current_query.value) {
|
if (!current_query.value) {
|
||||||
status_site.value = '↖ You need to type something'
|
status_site.value = '↖ You need to type something'
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
|
@ -178,16 +211,6 @@
|
||||||
width: '50%'
|
width: '50%'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!$('.select2-expansion').hasClass("select2-hidden-accessible")) {
|
|
||||||
$('.select2-expansion').select2({
|
|
||||||
theme: 'bootstrap-5'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!$('.select2-hover').hasClass("select2-hidden-accessible")) {
|
|
||||||
$('.select2-hover').select2({
|
|
||||||
theme: 'bootstrap-5'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#input_select').on('change.select2', async function (e) {
|
$('#input_select').on('change.select2', async function (e) {
|
||||||
attr_selected.value = $(this).select2('data').map(item => item.id)[0]
|
attr_selected.value = $(this).select2('data').map(item => item.id)[0]
|
||||||
|
@ -199,7 +222,8 @@
|
||||||
|
|
||||||
if (!$('.select2-modules').hasClass("select2-hidden-accessible")) {
|
if (!$('.select2-modules').hasClass("select2-hidden-accessible")) {
|
||||||
$('.select2-modules').select2({
|
$('.select2-modules').select2({
|
||||||
theme: 'bootstrap-5'
|
theme: 'bootstrap-5',
|
||||||
|
closeOnSelect: false
|
||||||
})
|
})
|
||||||
$('#modules_select').on('change.select2', async function (e) {
|
$('#modules_select').on('change.select2', async function (e) {
|
||||||
let loc_list = $(this).select2('data').map(item => item.id)
|
let loc_list = $(this).select2('data').map(item => item.id)
|
||||||
|
@ -207,7 +231,10 @@
|
||||||
for(let el in loc_list){
|
for(let el in loc_list){
|
||||||
for(let index in modules_list.value){
|
for(let index in modules_list.value){
|
||||||
if(modules_list.value[index].name == loc_list[el]){
|
if(modules_list.value[index].name == loc_list[el]){
|
||||||
if(modules_list.value[index].request_on_query){
|
if (own_config_bool){
|
||||||
|
config_query.value.push(modules_list.value[index])
|
||||||
|
}
|
||||||
|
else if(modules_list.value[index].request_on_query){
|
||||||
config_query.value.push(modules_list.value[index])
|
config_query.value.push(modules_list.value[index])
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -242,6 +269,43 @@
|
||||||
return loc
|
return loc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function add_entry(){
|
||||||
|
cp_entries.value += 1
|
||||||
|
}
|
||||||
|
function delete_entry(){
|
||||||
|
cp_entries.value -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function own_config(){
|
||||||
|
own_config_bool = !own_config_bool
|
||||||
|
if(own_config_bool){
|
||||||
|
let loc_list = $('#modules_select').val()
|
||||||
|
config_query.value = []
|
||||||
|
for(let el in loc_list){
|
||||||
|
for(let index in modules_list.value){
|
||||||
|
if(modules_list.value[index].name == loc_list[el]){
|
||||||
|
config_query.value.push(modules_list.value[index])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
let loc_list = $('#modules_select').val()
|
||||||
|
config_query.value = []
|
||||||
|
for(let el in loc_list){
|
||||||
|
for(let index in modules_list.value){
|
||||||
|
if(modules_list.value[index].name == loc_list[el]){
|
||||||
|
if(modules_list.value[index].request_on_query){
|
||||||
|
config_query.value.push(modules_list.value[index])
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message_list,
|
message_list,
|
||||||
|
@ -251,10 +315,15 @@
|
||||||
attr_selected,
|
attr_selected,
|
||||||
status_site,
|
status_site,
|
||||||
config_query,
|
config_query,
|
||||||
|
cp_entries,
|
||||||
|
queries,
|
||||||
actionQuery,
|
actionQuery,
|
||||||
pairedList,
|
pairedList,
|
||||||
checked_attr,
|
checked_attr,
|
||||||
generateCoreFormatUI
|
add_entry,
|
||||||
|
delete_entry,
|
||||||
|
generateCoreFormatUI,
|
||||||
|
own_config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).mount('.container-fluid')
|
}).mount('.container-fluid')
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<br> <br>
|
<br> <br>
|
||||||
<input type="hidden" id="share" value="{{sid}}">
|
<input type="hidden" id="share" value="{{sid}}">
|
||||||
<div id="top" style="display: ruby; margin-top: 40px;">
|
<div id="top" style="display: ruby; margin-top: 40px;">
|
||||||
<h2>{{query}}</h2>
|
<h2 title="{{', '.join(query)}}">{{query_str}}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group" style="float: right;" role="group" aria-label="Basic mixed styles example">
|
<div class="btn-group" style="float: right;" role="group" aria-label="Basic mixed styles example">
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
<div class="dropdown-menu p-4" style="min-width: 200px;">
|
<div class="dropdown-menu p-4" style="min-width: 200px;">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="query_as_params" class="form-label">To query:</label>
|
<label for="query_as_params" class="form-label">To query:</label>
|
||||||
<input type="email" class="form-control" id="query_as_params" placeholder="{{query}}">
|
<input type="email" class="form-control" id="query_as_params" placeholder="{{', '.join(query)}}">
|
||||||
<div id="query_as_params_error" style="color:brown"></div>
|
<div id="query_as_params_error" style="color:brown"></div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-sm" @click="query_as_params()" style="border-radius: 50px;">Query</button>
|
<button type="submit" class="btn btn-primary btn-sm" @click="query_as_params()" style="border-radius: 50px;">Query</button>
|
||||||
|
@ -54,9 +54,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="status_site" style="margin-left: 5px; font-size: 13px; float: right;">[[status_site]]</span>
|
<span v-if="status_site" style="margin-left: 5px; font-size: 13px; float: right;">[[status_site]]</span>
|
||||||
|
<br>
|
||||||
|
<button class="btn btn-secondary btn-sm" @click="send_flowintel_cm_all()" title="sendd all result to Flowintel-cm" style="float: right;">Flowintel-cm all</button>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
<!-- Offcanvas to navigate throw current search tree -->
|
||||||
<button class="btn btn-outline-primary" style="position: fixed; right: 0px; margin-top:30px" title="Session history" data-bs-toggle="offcanvas" data-bs-target="#offcanvasScrolling" aria-controls="offcanvasScrolling">
|
<button class="btn btn-outline-primary" style="position: fixed; right: 0px; margin-top:30px" title="Session history" data-bs-toggle="offcanvas" data-bs-target="#offcanvasScrolling" aria-controls="offcanvasScrolling">
|
||||||
<i class="fa-solid fa-bars"></i>
|
<i class="fa-solid fa-bars"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -71,7 +74,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="offcanvas-body">
|
<div class="offcanvas-body">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a :href="'/query/'+history.uuid" :title="'Attribute: \n' +history.input+ '\n\nModules: \n' + history.modules">[[history.query]]</a></li>
|
<li v-if="history.query"><a :href="'/query/'+history.uuid" :title="'Attribute: \n' +history.input+ '\n\nModules: \n' + history.modules">[[history.query.join(", ")]]</a></li>
|
||||||
<ul>
|
<ul>
|
||||||
<template v-for="child in history.children">
|
<template v-for="child in history.children">
|
||||||
<history_view :history="child"></history_view>
|
<history_view :history="child"></history_view>
|
||||||
|
@ -97,17 +100,26 @@
|
||||||
|
|
||||||
<div class="row" style="margin-bottom: 50px;">
|
<div class="row" style="margin-bottom: 50px;">
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<template v-if="tab_list == 'visual'">
|
|
||||||
<div data-bs-spy="scroll" data-bs-target="#list-result" data-bs-smooth-scroll="true" class="scrollspy-example" tabindex="0">
|
|
||||||
<div class="accordion" v-if="Object.keys(modules_res).length" style="width: 95%">
|
<div class="accordion" v-if="Object.keys(modules_res).length" style="width: 95%">
|
||||||
<div class="accordion-item" :id="'list-item-'+key" v-for="result, key in modules_res">
|
<div class="accordion-item" :id="'list-item-'+key_query" v-for="ele, key_query in modules_res">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpenMain-'+key_query" aria-expanded="true" :aria-controls="'panelsStayOpenMain-'+key_query">
|
||||||
|
[[key_query]]
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div :id="'panelsStayOpenMain-'+key_query" class="accordion-collapse collapse show">
|
||||||
|
<button class="btn btn-secondary" @click="send_flowintel_cm(key_query)" title="Send this result to Flowintel-cm" style="margin-top: 10px;margin-left: 10px;">Flowintel-cm</button>
|
||||||
|
<div class="accordion" style="padding: 25px">
|
||||||
|
<div class="accordion-item" :id="'list-item-'+key_query+'-'+key" v-for="result, key in ele">
|
||||||
<template v-if="!('error' in result)">
|
<template v-if="!('error' in result)">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpen-'+key" aria-expanded="true" :aria-controls="'panelsStayOpen-'+key">
|
<button class="accordion-button" style="height: 40px;" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpen-'+key_query+'-'+key" aria-expanded="true" :aria-controls="'panelsStayOpen-'+key">
|
||||||
[[key]]
|
[[key]]
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div :id="'panelsStayOpen-'+key" class="accordion-collapse collapse show">
|
<div :id="'panelsStayOpen-'+key_query+'-'+key" class="accordion-collapse collapse show">
|
||||||
|
<!-- visual part -->
|
||||||
|
<template v-if="tab_list == 'visual'">
|
||||||
<div class="accordion-body" >
|
<div class="accordion-body" >
|
||||||
<template v-if="'Object' in result.results">
|
<template v-if="'Object' in result.results">
|
||||||
<template v-for="obj in result.results.Object">
|
<template v-for="obj in result.results.Object">
|
||||||
|
@ -116,95 +128,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- <div class="accordion-body" v-html="generateCoreFormatUI(result)[0].outerHTML"></div> -->
|
|
||||||
<template v-for="misp_attrs, key_loop in result.results">
|
<template v-for="misp_attrs, key_loop in result.results">
|
||||||
<div v-for="misp_attr in misp_attrs.values" class="accordion-body" v-html="parseMispAttr(misp_attr, misp_attrs.types, key_loop, '/home/{{sid}}?query=', query_as_same)[0].outerHTML"></div>
|
<div v-for="misp_attr in misp_attrs.values" class="accordion-body" v-html="parseMispAttr(misp_attr, misp_attrs.types, key_loop, '/home/{{sid}}?query=', query_as_same)[0].outerHTML"></div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
<!-- json part -->
|
||||||
|
<template v-if="tab_list == 'json'">
|
||||||
|
<div class="btn-group" role="group" style="padding: 5px; margin-left: 5px; margin-top: 5px;">
|
||||||
|
<a class="btn btn-primary" :href="`/download/${sid}?module=${key}`" title="Download the json" >Download</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Errors Part -->
|
|
||||||
<hr style="margin-top: 50px; width: 95%">
|
|
||||||
<h3 id="errors_part">Errors</h3>
|
|
||||||
<div data-bs-spy="scroll" data-bs-target="#list-error" data-bs-smooth-scroll="true" class="scrollspy-example" tabindex="0">
|
|
||||||
<div class="accordion" style="width: 95%">
|
|
||||||
<div class="accordion-item" :id="'list-item-'+key" v-for="result, key in modules_res">
|
|
||||||
<template v-if="'error' in result">
|
|
||||||
<h2 class="accordion-header">
|
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpen-'+key" aria-expanded="true" :aria-controls="'panelsStayOpen-'+key">
|
|
||||||
[[key]]
|
|
||||||
<span style="margin-left: 5px;" title="Error">❌</span>
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div :id="'panelsStayOpen-'+key" class="accordion-collapse collapse show">
|
|
||||||
<div class="accordion-body" v-html="generateCoreFormatUI(result)[0].outerHTML"></div>
|
<div class="accordion-body" v-html="generateCoreFormatUI(result)[0].outerHTML"></div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
<!-- markdown part -->
|
||||||
</div>
|
<template v-if="tab_list == 'markdown'">
|
||||||
</div>
|
<div class="accordion-body">
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else-if="tab_list == 'json'">
|
|
||||||
<div v-if="Object.keys(modules_res).length">
|
|
||||||
<h3 id="results_part">Results</h3>
|
|
||||||
<div data-bs-spy="scroll" data-bs-target="#list-result" data-bs-smooth-scroll="true" class="scrollspy-example" tabindex="0">
|
|
||||||
<div class="accordion" style="width: 95%">
|
|
||||||
<div class="accordion-item" :id="'list-item-'+key" v-for="result, key in modules_res">
|
|
||||||
<template v-if="!('error' in result)">
|
|
||||||
<h2 class="accordion-header">
|
|
||||||
<button class="accordion-button" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpen-'+key" aria-expanded="true" :aria-controls="'panelsStayOpen-'+key">
|
|
||||||
[[key]]
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div :id="'panelsStayOpen-'+key" class="accordion-collapse collapse show">
|
|
||||||
<a class="btn btn-primary" :href="`/download/${sid}?module=${key}`" title="Download the json" style="padding: 5px; margin-left: 5px; margin-top: 5px;">Download</a>
|
|
||||||
<div class="accordion-body" v-html="generateCoreFormatUI(result)[0].outerHTML"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Errors Part -->
|
|
||||||
<hr style="margin-top: 50px; width: 95%">
|
|
||||||
<h3 id="errors_part">Errors</h3>
|
|
||||||
<div data-bs-spy="scroll" data-bs-target="#list-error" data-bs-smooth-scroll="true" class="scrollspy-example" tabindex="0">
|
|
||||||
<div class="accordion" style="width: 95%">
|
|
||||||
<div class="accordion-item" :id="'list-item-'+key" v-for="result, key in modules_res">
|
|
||||||
<template v-if="'error' in result">
|
|
||||||
<h2 class="accordion-header">
|
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpen-'+key" aria-expanded="true" :aria-controls="'panelsStayOpen-'+key">
|
|
||||||
[[key]]
|
|
||||||
<span style="margin-left: 5px;" title="Error">❌</span>
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div :id="'panelsStayOpen-'+key" class="accordion-collapse collapse show">
|
|
||||||
<div class="accordion-body" v-html="generateCoreFormatUI(result)[0].outerHTML"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else-if="tab_list == 'markdown'">
|
|
||||||
<div v-if="Object.keys(modules_res).length" class="accordion" style="width: 95%">
|
|
||||||
<div class="accordion-item" :id="'list-item-'+key" v-for="result, key in modules_res">
|
|
||||||
<template v-if="!('error' in result)">
|
|
||||||
<h2 class="accordion-header">
|
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpen-'+key" aria-expanded="true" :aria-controls="'panelsStayOpen-'+key">
|
|
||||||
[[key]]
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div :id="'panelsStayOpen-'+key" class="accordion-collapse collapse show">
|
|
||||||
<div class="accordion-body row">
|
|
||||||
<template v-if="'Object' in result.results">
|
<template v-if="'Object' in result.results">
|
||||||
<template v-for="obj, key_obj in result.results.Object">
|
<template v-for="obj, key_obj in result.results.Object">
|
||||||
<pre>
|
<pre>
|
||||||
|
@ -230,32 +169,93 @@ Value: [[misp_attr]]
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</template> <!-- Not error -->
|
||||||
|
</div>
|
||||||
|
</div> <!-- Accordion -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Errors Part -->
|
||||||
|
<hr style="margin-top: 50px; width: 95%">
|
||||||
|
<h3 id="errors_part">Errors</h3>
|
||||||
|
<div class="accordion" style="width: 95%">
|
||||||
|
<div class="accordion-item" :id="'list-item-'+key_query" v-for="ele, key_query in modules_res">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpenMainError-'+key_query" aria-expanded="false" :aria-controls="'panelsStayOpenMainError-'+key_query">
|
||||||
|
[[key_query]]
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div :id="'panelsStayOpenMainError-'+key_query" class="accordion-collapse collapse">
|
||||||
|
<div class="accordion" style="padding: 25px">
|
||||||
|
<div class="accordion-item" :id="'list-item-'+key_query+'-'+key" v-for="result, key in ele">
|
||||||
|
<template v-if="'error' in result">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button" style="height: 40px;" data-bs-toggle="collapse" :data-bs-target="'#panelsStayOpenError-'+key_query+'-'+key" aria-expanded="false" :aria-controls="'panelsStayOpenError-'+key_query+'-'+key">
|
||||||
|
[[key]]
|
||||||
|
<span style="margin-left: 5px;" title="Error">❌</span>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div :id="'panelsStayOpenError-'+key_query+'-'+key" class="accordion-collapse collapse">
|
||||||
|
<template v-if="tab_list == 'visual' || tasb_list == 'json'">
|
||||||
|
<div class="accordion-body" v-html="generateCoreFormatUI(result)[0].outerHTML"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- right menu -->
|
||||||
<div id="list-result" class="list-group col-2" style="position: fixed; right: 0px;">
|
<div id="list-result" class="list-group col-2" style="position: fixed; right: 0px;">
|
||||||
<a class="list-group-item list-group-item-action" v-if="tab_list == 'json'" style="background-color: #0d6efd; color:white" href="#results_part">Results</a>
|
|
||||||
|
|
||||||
<template v-for="result, key in modules_res">
|
<template v-for="result, key in modules_res">
|
||||||
<a class="list-group-item list-group-item-action" v-if="!('error' in result)" :href="'#list-item-'+key">[[key]]</a>
|
<a class="btn btn-outline-primary" data-bs-toggle="collapse" :href="'#collapse-menu-'+key" role="button" aria-expanded="false" :aria-controls="'collapse-menu-'+key">
|
||||||
|
<template v-if="key.length > 20">
|
||||||
|
[[key.substring(0, 21)]]...
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
[[key]]
|
||||||
|
</template>
|
||||||
|
</a>
|
||||||
|
<div class="collapse" :id="'collapse-menu-'+key">
|
||||||
|
<div class="card card-body">
|
||||||
|
<template v-for="res, key_sub in result">
|
||||||
|
<a class="list-group-item list-group-item-action" v-if="!('error' in res)" :href="'#list-item-'+key+'-'+key_sub">[[key_sub]]</a>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="tab_list == 'json' || tab_list == 'visual'">
|
<template v-if="tab_list == 'json' || tab_list == 'visual'">
|
||||||
<a class="list-group-item list-group-item-action" style="background-color: #0d6efd; color:white" href="#errors_part">Errors</a>
|
<a class="btn btn-primary" style="background-color: #0d6efd; color:white" href="#errors_part">Errors</a>
|
||||||
<div id="list-error" class="list-group">
|
|
||||||
<template v-for="result, key in modules_res">
|
<template v-for="result, key in modules_res">
|
||||||
<a class="list-group-item list-group-item-action" v-if="'error' in result" :href="'#list-item-'+key" style="border-radius: 0;">[[key]]</a>
|
<a class="btn btn-outline-primary" data-bs-toggle="collapse" :href="'#collapse-menu-error-'+key" role="button" aria-expanded="false" :aria-controls="'collapse-menu-error-'+key">
|
||||||
|
<template v-if="key.length > 20">
|
||||||
|
[[key.substring(0, 21)]]...
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
[[key]]
|
||||||
|
</template>
|
||||||
|
</a>
|
||||||
|
<div class="collapse" :id="'collapse-menu-error-'+key">
|
||||||
|
<div class="card card-body">
|
||||||
|
<template v-for="res, key_sub in result">
|
||||||
|
<a class="list-group-item list-group-item-action" v-if="'error' in res" :href="'#list-item-'+key+'-'+key_sub">[[key_sub]]</a>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span id="goTop">[<a href="#top">Go Back Top</a>]</span>
|
<span id="goTop">[<a href="#top">Go Back Top</a>]</span>
|
||||||
|
<div id="insert_form"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
|
@ -359,7 +359,7 @@ Value: [[misp_attr]]
|
||||||
async function query_as_same(value){
|
async function query_as_same(value){
|
||||||
let result_dict = {"modules": query_info.value["modules"],
|
let result_dict = {"modules": query_info.value["modules"],
|
||||||
"input": query_info.value["input_query"],
|
"input": query_info.value["input_query"],
|
||||||
"query": value,
|
"query": [value],
|
||||||
"parent_id": sid.value,
|
"parent_id": sid.value,
|
||||||
"query_as_same": true,
|
"query_as_same": true,
|
||||||
"config": {}
|
"config": {}
|
||||||
|
@ -379,6 +379,7 @@ Value: [[misp_attr]]
|
||||||
// query 'value' with same parameters without a parent
|
// query 'value' with same parameters without a parent
|
||||||
async function query_as_params(){
|
async function query_as_params(){
|
||||||
let loc = $("#query_as_params").val()
|
let loc = $("#query_as_params").val()
|
||||||
|
loc = loc.split(', ')
|
||||||
$("#query_as_params_error").text("")
|
$("#query_as_params_error").text("")
|
||||||
if(loc){
|
if(loc){
|
||||||
let result_dict = {"modules": query_info.value["modules"],
|
let result_dict = {"modules": query_info.value["modules"],
|
||||||
|
@ -403,6 +404,29 @@ Value: [[misp_attr]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sender_flowintel(to_return){
|
||||||
|
const res = await fetch('/flowintel_url')
|
||||||
|
let loc = await res.json()
|
||||||
|
const flowintel_cm_url = loc["url"]
|
||||||
|
|
||||||
|
$('#insert_form').append(
|
||||||
|
$('<form>').attr({"action": flowintel_cm_url, "name": "flowintel", "method": "post", "style": "display:none"}).append(
|
||||||
|
$("<input>").attr({"type": "text", "name": "result"}).val(JSON.stringify(to_return))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
document.forms['flowintel'].submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function send_flowintel_cm(key){
|
||||||
|
let to_return = {}
|
||||||
|
to_return[key] = modules_res.value[key]
|
||||||
|
sender_flowintel(to_return)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function send_flowintel_cm_all(){
|
||||||
|
sender_flowintel(modules_res.value)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
queryInfo()
|
queryInfo()
|
||||||
actionQuery()
|
actionQuery()
|
||||||
|
@ -425,7 +449,9 @@ Value: [[misp_attr]]
|
||||||
parseMispAttr,
|
parseMispAttr,
|
||||||
active_tab,
|
active_tab,
|
||||||
query_as_same,
|
query_as_same,
|
||||||
query_as_params
|
query_as_params,
|
||||||
|
send_flowintel_cm,
|
||||||
|
send_flowintel_cm_all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).mount('.container-fluid')
|
}).mount('.container-fluid')
|
||||||
|
|
|
@ -12,6 +12,7 @@ CONF_PATH = os.path.join(os.getcwd(), "conf", "config.cfg")
|
||||||
config.read(CONF_PATH)
|
config.read(CONF_PATH)
|
||||||
|
|
||||||
MODULES = []
|
MODULES = []
|
||||||
|
FLOWINTEL_URL = Config.FLOWINTEL_URL
|
||||||
|
|
||||||
def query_get_module(headers={'Content-type': 'application/json'}):
|
def query_get_module(headers={'Content-type': 'application/json'}):
|
||||||
global MODULES
|
global MODULES
|
||||||
|
|
|
@ -4,6 +4,7 @@ class Config:
|
||||||
FLASK_URL = '127.0.0.1'
|
FLASK_URL = '127.0.0.1'
|
||||||
FLASK_PORT = 7008
|
FLASK_PORT = 7008
|
||||||
MISP_MODULE = '127.0.0.1:6666'
|
MISP_MODULE = '127.0.0.1:6666'
|
||||||
|
FLOWINTEL_URL = 'http://localhost:7006'
|
||||||
|
|
||||||
QUERIES_LIMIT = 200
|
QUERIES_LIMIT = 200
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ function launch {
|
||||||
export FLASKENV="development"
|
export FLASKENV="development"
|
||||||
killscript
|
killscript
|
||||||
screen -dmS "misp_mod"
|
screen -dmS "misp_mod"
|
||||||
screen -S "misp_mod" -X screen -t "misp_modules_server" bash -c "../env/bin/misp-modules -l 127.0.0.1; read x"
|
screen -S "misp_mod" -X screen -t "misp_modules_server" bash -c "misp-modules -l 127.0.0.1; read x"
|
||||||
sleep 2
|
sleep 2
|
||||||
python3 app.py -m
|
python3 app.py -m
|
||||||
python3 app.py
|
python3 app.py
|
||||||
|
|
|
@ -6,7 +6,7 @@ Flask-WTF
|
||||||
Flask-Migrate
|
Flask-Migrate
|
||||||
Flask-Login
|
Flask-Login
|
||||||
WTForms
|
WTForms
|
||||||
Werkzeug==2.3.8
|
Werkzeug==3.0.3
|
||||||
flask-restx
|
flask-restx
|
||||||
python-dateutil
|
python-dateutil
|
||||||
schedule
|
schedule
|
||||||
|
|
Loading…
Reference in New Issue