Merge branch 'main' of github.com:MISP/misp-modules

pull/667/head
Alexandre Dulaunoy 2024-06-06 07:41:20 +02:00
commit f023c2ba5d
No known key found for this signature in database
GPG Key ID: 09E2CD4944E6CBCD
26 changed files with 739 additions and 365 deletions

View File

@ -3,7 +3,7 @@ aiohttp>=3.9.0
aiosignal==1.3.1 ; python_version >= '3.7'
antlr4-python3-runtime==4.9.3
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
argcomplete==3.0.8 ; python_version >= '3.6'
argparse==1.4.0

View File

@ -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/)
* [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.
* [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.
* [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?

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -260,7 +260,7 @@ Module to expand country codes.
An expansion module to query the CVE search API with a cpe code to get its related vulnerabilities.
- **features**:
>The module takes a cpe attribute as input and queries the CVE search API to get its related vulnerabilities.
>The module takes a cpe attribute as input and queries the CVE search API to get its related vulnerabilities.
>The list of vulnerabilities is then parsed and returned as vulnerability objects.
>
>Users can use their own CVE search API url by defining a value to the custom_API_URL parameter. If no custom API url is given, the default cve.circl.lu api url is used.
@ -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)
<img src=logos/google.png height=60>
- **descrption**:
>A hover module to get information about an url using a Google search.
- **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)
<img src=logos/greynoise.png height=60>
@ -745,7 +767,7 @@ Expansion module to fetch the html content from an url and convert it into markd
HYAS Insight integration to MISP provides direct, high volume access to HYAS Insight data. It enables investigators and analysts to understand and defend against cyber adversaries and their infrastructure.
- **features**:
>This Module takes the IP Address, Domain, URL, Email, Phone Number, MD5, SHA1, Sha256, SHA512 MISP Attributes as input to query the HYAS Insight API.
> The results of the HYAS Insight API are than are then returned and parsed into Hyas Insight Objects.
> The results of the HYAS Insight API are than are then returned and parsed into Hyas Insight Objects.
>
>An API key is required to submit queries to the HYAS Insight API.
>
@ -819,9 +841,9 @@ Module to access intelmqs eventdb.
An expansion module to query IP2Location.io to gather more information on a given IP address.
- **features**:
>The module takes an IP address attribute as input and queries the IP2Location.io API.
>Free plan user will get the basic geolocation informaiton, and different subsription plan will get more information on the IP address.
> Refer to [pricing page](https://www.ip2location.io/pricing) for more information on data available for each plan.
>The module takes an IP address attribute as input and queries the IP2Location.io API.
>Free plan user will get the basic geolocation informaiton, and different subsription plan will get more information on the IP address.
> Refer to [pricing page](https://www.ip2location.io/pricing) for more information on data available for each plan.
>
>More information on the responses content is available in the [documentation](https://www.ip2location.io/ip2location-documentation).
- **input**:
@ -857,7 +879,7 @@ Module to query an IP ASN history service (https://github.com/D4-project/IPASN-H
An expansion module to query ipinfo.io to gather more information on a given IP address.
- **features**:
>The module takes an IP address attribute as input and queries the ipinfo.io API.
>The module takes an IP address attribute as input and queries the ipinfo.io API.
>The geolocation information on the IP address is always returned.
>
>Depending on the subscription plan, the API returns different pieces of information then:
@ -883,7 +905,7 @@ An expansion module to query ipinfo.io to gather more information on a given IP
IPQualityScore MISP Expansion Module for IP reputation, Email Validation, Phone Number Validation, Malicious Domain and Malicious URL Scanner.
- **features**:
>This Module takes the IP Address, Domain, URL, Email and Phone Number MISP Attributes as input to query the IPQualityScore API.
> The results of the IPQualityScore API are than returned as IPQS Fraud and Risk Scoring Object.
> The results of the IPQualityScore API are than returned as IPQS Fraud and Risk Scoring Object.
> The object contains a copy of the enriched attribute with added tags presenting the verdict based on fraud score,risk score and other attributes from IPQualityScore.
- **input**:
>A MISP attribute of type IP Address(ip-src, ip-dst), Domain(hostname, domain), URL(url, uri), Email Address(email, email-src, email-dst, target-email, whois-registrant-email) and Phone Number(phone-number, whois-registrant-phone).
@ -1222,7 +1244,7 @@ Module to get information from AlienVault OTX.
An expansion module to query the CIRCL Passive SSH.
- **features**:
>The module queries the Passive SSH service from CIRCL.
>
>
> The module can be used an hover module but also an expansion model to add related MISP objects.
>
- **input**:
@ -1965,7 +1987,7 @@ Module to query a local instance of uwhois (https://github.com/rafiot/uwhoisd).
<img src=logos/whoisfreaks.png height=60>
An expansion module for https://whoisfreaks.com/ that will provide an enriched analysis of the provided domain, including WHOIS and DNS information.
Our Whois service, DNS Lookup API, and SSL analysis, equips organizations with comprehensive threat intelligence and attack surface analysis capabilities for enhanced security.
Our Whois service, DNS Lookup API, and SSL analysis, equips organizations with comprehensive threat intelligence and attack surface analysis capabilities for enhanced security.
Explore our website's product section at https://whoisfreaks.com/ for a wide range of additional services catering to threat intelligence and attack surface analysis needs.
- **features**:
>The module takes a domain as input and queries the Whoisfreaks API with it.
@ -2104,7 +2126,7 @@ Module to process a query on Yeti.
> - https://github.com/sebdraven/pyeti
- **requirements**:
> - pyeti
> - API key
> - API key
-----
@ -2261,7 +2283,7 @@ Simple export of a MISP event to PDF.
> 'Activate_galaxy_description' is a boolean (True or void) to activate the description of event related galaxies.
> 'Activate_related_events' is a boolean (True or void) to activate the description of related event. Be aware this might leak information on confidential events linked to the current event !
> 'Activate_internationalization_fonts' is a boolean (True or void) to activate Noto fonts instead of default fonts (Helvetica). This allows the support of CJK alphabet. Be sure to have followed the procedure to download Noto fonts (~70Mo) in the right place (/tools/pdf_fonts/Noto_TTF), to allow PyMisp to find and use them during PDF generation.
> 'Custom_fonts_path' is a text (path or void) to the TTF file of your choice, to create the PDF with it. Be aware the PDF won't support bold/italic/special style anymore with this option
> 'Custom_fonts_path' is a text (path or void) to the TTF file of your choice, to create the PDF with it. Be aware the PDF won't support bold/italic/special style anymore with this option
- **input**:
>MISP Event
- **output**:

View File

@ -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)"
}

View File

@ -20,7 +20,8 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c
'trustar_enrich', 'recordedfuture', 'html_to_markdown', 'socialscan', 'passive-ssh',
'qintel_qsentry', 'mwdb', 'hashlookup', 'mmdb_lookup', 'ipqs_fraud_and_risk_scoring',
'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')

View File

@ -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())

View File

@ -19,6 +19,8 @@ git submodule init && git submodule update ## Initialize misp-objects submodul
python3 app.py -i ## Initialize db
```
Don't forget to install **misp-modules**...
## Config
Edit `config.py`
@ -35,8 +37,6 @@ Edit `config.py`
- `ADMIN_PASSWORD`: Password for Admin user if `ADMIN_USER` is True
Rename `config.cfg.sample` to `config.cfg` then edit it:
- `ADMIN_USER`: If True, config page will not be accessible

View File

@ -37,6 +37,7 @@ def create_app():
app.register_blueprint(home_blueprint, url_prefix="/")
app.register_blueprint(history_blueprint, url_prefix="/")
app.register_blueprint(account_blueprint, url_prefix="/")
csrf.exempt(home_blueprint)
return app

View File

@ -38,7 +38,7 @@ class Session_db(db.Model):
"id": self.id,
"uuid": self.uuid,
"modules": json.loads(self.modules_list),
"query_enter": self.query_enter,
"query_enter": json.loads(self.query_enter),
"input_query": self.input_query,
"config_module": json.loads(self.config_module),
"result": json.loads(self.result),
@ -51,7 +51,7 @@ class Session_db(db.Model):
json_dict = {
"uuid": self.uuid,
"modules": json.loads(self.modules_list),
"query": self.query_enter,
"query": json.loads(self.query_enter),
"input": self.input_query,
"query_date": self.query_date.strftime('%Y-%m-%d %H:%M')
}

View File

@ -146,8 +146,8 @@ def util_remove_node_session(node_uuid, parent, parent_path):
child = parent["children"][i]
if child["uuid"] == node_uuid:
del parent_path["children"][i]
return
elif child["children"]:
return True
elif "children" in child and child["children"]:
return util_remove_node_session(node_uuid, child, parent_path["children"][i])
def remove_node_session(node_uuid):
@ -160,7 +160,9 @@ def remove_node_session(node_uuid):
loc = i
break
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:
del sess[keys_list[i]]

View File

@ -1,9 +1,10 @@
import ast
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 . import session_class as SessionModel
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',
@ -13,18 +14,35 @@ home_blueprint = Blueprint(
)
@home_blueprint.route("/")
@home_blueprint.route("/", methods=["GET", "POST"])
def home():
try:
del sess["query"]
except:
pass
sess["admin_user"] = bool(admin_user_active())
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")
@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"])
def home_query(sid):
try:
del sess["query"]
except:
pass
sess["admin_user"] = admin_user_active()
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("404.html")
@ -33,21 +51,28 @@ def query(sid):
sess["admin_user"] = admin_user_active()
session = HomeModel.get_session(sid)
flag=False
modules_list = []
if session:
flag = True
query_loc = session.query_enter
query_loc = json.loads(session.query_enter)
modules_list = json.loads(session.modules_list)
else:
for s in SessionModel.sessions:
if s.uuid == sid:
flag = True
query_loc = s.query
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:
return render_template("query.html",
query=query_loc,
query_str=query_str,
sid=sid,
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'))
return render_template("404.html")
@ -60,18 +85,20 @@ def get_query_info(sid):
flag=False
if session:
flag = True
query_loc = session.query_enter
query_loc = json.loads(session.query_enter)
modules_list = json.loads(session.modules_list)
else:
for s in SessionModel.sessions:
if s.uuid == sid:
flag = True
query_loc = s.query
modules_list = s.modules_list
session=s
if flag:
loc_dict = {
"query": query_loc,
"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')
}
return loc_dict
@ -227,3 +254,9 @@ def change_status():
return {'message': 'Something went wrong', 'toast_class': "danger-subtle"}, 400
return {'message': 'Need to pass "module_id"', 'toast_class': "warning-subtle"}, 400
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

View File

@ -163,7 +163,7 @@ def create_new_session_tree(current_session, parent_id):
loc_json = {
"uuid": loc_session.uuid,
"modules": json.loads(loc_session.modules_list),
"query": loc_session.query_enter,
"query": json.loads(loc_session.query_enter),
"input": loc_session.input_query,
"query_date": loc_session.query_date.strftime('%Y-%m-%d %H:%M'),
"config": json.loads(loc_session.config_module),

View File

@ -64,9 +64,12 @@ class Session_class:
def start(self):
"""Start all worker"""
for i in range(len(self.modules_list)):
#need the index and the url in each queue item.
self.jobs.put((i, self.modules_list[i]))
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.
for _ in range(self.thread_count):
worker = Thread(target=self.process)
worker.daemon = True
@ -111,44 +114,44 @@ class Session_class:
modules = query_get_module()
loc_query = {}
self.result[work[1]] = dict()
# If Misp format
for module in modules:
if module["name"] == work[1]:
if module["name"] == work[2]:
if "format" in module["mispattributes"]:
loc_query = {
"type": self.input_query,
"value": self.query,
"value": work[1],
"uuid": str(uuid.uuid4())
}
break
loc_config = {}
if work[1] in self.config_module:
loc_config = self.config_module[work[1]]
if work[2] in self.config_module:
loc_config = self.config_module[work[2]]
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:
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)
## Sort attr in object by ui-priority
if "results" in res:
if "Object" in res["results"]:
for obj in res["results"]["Object"]:
loc_obj = get_object(obj["name"])
if loc_obj:
for attr in obj["Attribute"]:
attr["ui-priority"] = loc_obj["attributes"][attr["object_relation"]]["ui-priority"]
# After adding 'ui-priority'
obj["Attribute"].sort(key=lambda x: x["ui-priority"], reverse=True)
if res:
if "results" in res:
if "Object" in res["results"]:
for obj in res["results"]["Object"]:
loc_obj = get_object(obj["name"])
if loc_obj:
for attr in obj["Attribute"]:
attr["ui-priority"] = loc_obj["attributes"][attr["object_relation"]]["ui-priority"]
# After adding 'ui-priority'
obj["Attribute"].sort(key=lambda x: x["ui-priority"], reverse=True)
# print(res)
if "error" in res:
if res and "error" in res:
self.nb_errors += 1
self.result[work[1]] = res
self.result[work[1]][work[2]] = res
self.jobs.task_done()
return True
@ -161,7 +164,7 @@ class Session_class:
s = Session_db(
uuid=str(self.uuid),
modules_list=json.dumps(self.modules_list),
query_enter=self.query,
query_enter=json.dumps(self.query),
input_query=self.input_query,
config_module=json.dumps(self.config_module),
result=json.dumps(self.result),

View File

@ -6,7 +6,7 @@ export default {
},
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>
<template v-for="child in history.children">
<history_view :history="child"></history_view>

View File

@ -48,7 +48,7 @@ export default {
<ul class="list-group list-group-horizontal" style="padding-top: 5px;">
<li class="list-group-item">
<h5>[[history.query]]</h5>
<h5>[[history.query.join(", ")]]</h5>
</li>
<li class="list-group-item">
<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="card card-body">
<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>
</div>
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>

View File

@ -10,13 +10,15 @@ function parseMispObject(misp_object, query_url, functionToCall){
if(query_url){
$query=$("<a>").attr("href", query_url+v.value).text("query").css("margin-left", "10px")
}
// `_${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}')`,
"title": "Query this value with the same attribute and modules as the main query",
"class": "btn btn-link"
})
.text("query as same")
.css({"margin-left": "10px", "padding": "0", "--bs-btn-border-width": "0"})
if(functionToCall){
// `_${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}')`,
"title": "Query this value with the same attribute and modules as the main query",
"class": "btn btn-link"
})
.text("query as same")
.css({"margin-left": "10px", "padding": "0", "--bs-btn-border-width": "0"})
}
}
$container.append(
@ -74,12 +76,14 @@ 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")
}
// `_${functionToCall.name}('${misp_attr}')` refer to 'window._query_as_same = query_as_same' in my vue file
$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",
"class": "btn btn-link"
})
.text("query as same")
.css({"margin-left": "10px", "padding": "0", "--bs-btn-border-width": "0"})
if(query_as_same){
$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",
"class": "btn btn-link"
})
.text("query as same")
.css({"margin-left": "10px", "padding": "0", "--bs-btn-border-width": "0"})
}
}

View File

@ -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>
`
}

View File

@ -30,7 +30,7 @@
<div class="list-group" style="margin-bottom: 20px;">
<a :href="'/query/'+h.uuid" class="list-group-item list-group-item-action">
<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>
</div>
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>

View File

@ -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">
<ul class="list-group list-group-horizontal">
<li class="list-group-item">
<h4>[[his.query]]</h4>
<h4>[[his.query.join(", ")]]</h4>
</li>
<li class="list-group-item">
<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="card card-body">
<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>
</div>
<p class="mb-1" style="color: green;"><u>Input Attribute</u>:</p>

View File

@ -19,10 +19,15 @@
</div>
<div class="col-11">
<div style="width:50%; transform: translate(50%, 0);">
<div>
<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;" />
</div>
<template v-for="elem in cp_entries">
<div style="margin-top: 10px;">
<input type="hidden" id="parent_id" value="{{sid}}">
<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>
</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>
</div>
@ -53,9 +58,14 @@
</select>
</div>
</template>
<div title="Configure modules even if a configuration as already been given" style="margin-top: 10px; transform: translate(25%, 0);">
<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 reauest_on_query activate -->
<div v-if="config_query.length" style="margin-top: 10px; padding: 5px" class="row">
<!-- 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>
<div class="card col-4" style="margin-top: 10px; padding: 15px" v-for="module in config_query">
<h4>[[module.name]]</h4>
@ -86,14 +96,37 @@
const progress = ref(0)
const current_query = ref()
const current_query = ref([])
const status_site = 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(){
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) {
status_site.value = '↖ You need to type something'
window.scrollTo(0, 0);
@ -178,16 +211,6 @@
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) {
attr_selected.value = $(this).select2('data').map(item => item.id)[0]
@ -199,7 +222,8 @@
if (!$('.select2-modules').hasClass("select2-hidden-accessible")) {
$('.select2-modules').select2({
theme: 'bootstrap-5'
theme: 'bootstrap-5',
closeOnSelect: false
})
$('#modules_select').on('change.select2', async function (e) {
let loc_list = $(this).select2('data').map(item => item.id)
@ -207,7 +231,10 @@
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){
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])
}
break
@ -215,7 +242,7 @@
}
}
})
}
}
})
$('#input_select').on('select2:open', function (e) {
@ -242,6 +269,43 @@
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 {
message_list,
@ -251,10 +315,15 @@
attr_selected,
status_site,
config_query,
cp_entries,
queries,
actionQuery,
pairedList,
checked_attr,
generateCoreFormatUI
add_entry,
delete_entry,
generateCoreFormatUI,
own_config
}
}
}).mount('.container-fluid')

View File

@ -8,7 +8,7 @@
<br> <br>
<input type="hidden" id="share" value="{{sid}}">
<div id="top" style="display: ruby; margin-top: 40px;">
<h2>{{query}}</h2>
<h2 title="{{', '.join(query)}}">{{query_str}}</h2>
</div>
<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="mb-3">
<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>
<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>
<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/>
<!-- 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">
<i class="fa-solid fa-bars"></i>
</button>
@ -71,7 +74,7 @@
</div>
<div class="offcanvas-body">
<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>
<template v-for="child in history.children">
<history_view :history="child"></history_view>
@ -96,166 +99,163 @@
</ul>
<div class="row" style="margin-bottom: 50px;">
<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-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">
<div class="accordion-body" >
<template v-if="'Object' in result.results">
<template v-for="obj in result.results.Object">
<div v-html="parseMispObject(obj, '/home/{{sid}}?query=', query_as_same)[0].outerHTML"></div>
</template>
<div class="col-10">
<div class="accordion" v-if="Object.keys(modules_res).length" 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" 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)">
<h2 class="accordion-header">
<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]]
</button>
</h2>
<div :id="'panelsStayOpen-'+key_query+'-'+key" class="accordion-collapse collapse show">
<!-- visual part -->
<template v-if="tab_list == 'visual'">
<div class="accordion-body" >
<template v-if="'Object' in result.results">
<template v-for="obj in result.results.Object">
<div v-html="parseMispObject(obj, '/home/{{sid}}?query=', query_as_same)[0].outerHTML"></div>
</template>
</template>
<template v-else>
<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>
</template>
</template>
</div>
</template>
<template v-else>
<!-- <div class="accordion-body" v-html="generateCoreFormatUI(result)[0].outerHTML"></div> -->
<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>
</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 class="accordion-body" v-html="generateCoreFormatUI(result)[0].outerHTML"></div>
</template>
</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>
</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-for="obj, key_obj in result.results.Object">
<pre>
<!-- markdown part -->
<template v-if="tab_list == 'markdown'">
<div class="accordion-body">
<template v-if="'Object' in result.results">
<template v-for="obj, key_obj in result.results.Object">
<pre>
#### [[obj.name]]
<template v-for="attr, key_attr in obj.Attribute">
###### [[attr.object_relation]]
Type: [[attr.type]]
Value: [[attr.value]]
</template>
</pre>
<hr>
</template>
</template>
<template v-else>
<template v-for="misp_attrs, key_loop in result.results">
<template v-for="misp_attr in misp_attrs.values">
<pre>
</pre>
<hr>
</template>
</template>
<template v-else>
<template v-for="misp_attrs, key_loop in result.results">
<template v-for="misp_attr in misp_attrs.values">
<pre>
#### Attr [[key_loop +1]]
Type: [[misp_attrs.types.join(", ")]]
Value: [[misp_attr]]
</pre>
</template>
</pre>
</template>
</template>
</template>
</div>
</template>
</template>
</div>
</div>
</template> <!-- Not error -->
</div>
</div> <!-- Accordion -->
</div>
</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;">
<template v-for="result, key in modules_res">
<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>
</div>
<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">
<a class="list-group-item list-group-item-action" v-if="!('error' in result)" :href="'#list-item-'+key">[[key]]</a>
</template>
<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>
<div id="list-error" class="list-group">
<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>
</template>
</div>
<a class="btn btn-primary" style="background-color: #0d6efd; color:white" href="#errors_part">Errors</a>
<template v-for="result, key in modules_res">
<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>
</div>
</div>
</template>
</template>
</div>
</div>
</div>
<span id="goTop">[<a href="#top">Go Back Top</a>]</span>
<div id="insert_form"></div>
{% endblock %}
{% block script %}
@ -359,7 +359,7 @@ Value: [[misp_attr]]
async function query_as_same(value){
let result_dict = {"modules": query_info.value["modules"],
"input": query_info.value["input_query"],
"query": value,
"query": [value],
"parent_id": sid.value,
"query_as_same": true,
"config": {}
@ -379,6 +379,7 @@ Value: [[misp_attr]]
// query 'value' with same parameters without a parent
async function query_as_params(){
let loc = $("#query_as_params").val()
loc = loc.split(', ')
$("#query_as_params_error").text("")
if(loc){
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(() => {
queryInfo()
actionQuery()
@ -425,7 +449,9 @@ Value: [[misp_attr]]
parseMispAttr,
active_tab,
query_as_same,
query_as_params
query_as_params,
send_flowintel_cm,
send_flowintel_cm_all
}
}
}).mount('.container-fluid')

View File

@ -12,6 +12,7 @@ CONF_PATH = os.path.join(os.getcwd(), "conf", "config.cfg")
config.read(CONF_PATH)
MODULES = []
FLOWINTEL_URL = Config.FLOWINTEL_URL
def query_get_module(headers={'Content-type': 'application/json'}):
global MODULES

View File

@ -4,6 +4,7 @@ class Config:
FLASK_URL = '127.0.0.1'
FLASK_PORT = 7008
MISP_MODULE = '127.0.0.1:6666'
FLOWINTEL_URL = 'http://localhost:7006'
QUERIES_LIMIT = 200

View File

@ -11,7 +11,7 @@ function launch {
export FLASKENV="development"
killscript
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
python3 app.py -m
python3 app.py

View File

@ -6,7 +6,7 @@ Flask-WTF
Flask-Migrate
Flask-Login
WTForms
Werkzeug==2.3.8
Werkzeug==3.0.3
flask-restx
python-dateutil
schedule