new: [app:evaluator_query-search] Added query-search evaluator and made full evaluation chain async

This commit is contained in:
Sami Mokaddem 2024-07-04 14:49:26 +02:00
parent 8774d70759
commit 99f9751e22
5 changed files with 36 additions and 29 deletions

View file

@ -2,7 +2,6 @@
import functools import functools
import time import time
from collections import defaultdict
from pathlib import Path from pathlib import Path
import json import json
import re import re
@ -312,9 +311,9 @@ def get_progress():
return progress return progress
def check_inject(user_id: int, inject: dict, data: dict, context: dict) -> bool: async def check_inject(user_id: int, inject: dict, data: dict, context: dict) -> bool:
for inject_evaluation in inject['inject_evaluation']: for inject_evaluation in inject['inject_evaluation']:
success = inject_checker_router(user_id, inject_evaluation, data, context) success = await inject_checker_router(user_id, inject_evaluation, data, context)
if not success: if not success:
logger.info(f"Task not completed: {inject['uuid']}") logger.info(f"Task not completed: {inject['uuid']}")
return False return False
@ -338,14 +337,14 @@ def is_valid_evaluation_context(user_id: int, inject_evaluation: dict, data: dic
return False return False
return False return False
def inject_checker_router(user_id: int, inject_evaluation: dict, data: dict, context: dict) -> bool: async def inject_checker_router(user_id: int, inject_evaluation: dict, data: dict, context: dict) -> bool:
if not is_valid_evaluation_context(user_id, inject_evaluation, data, context): if not is_valid_evaluation_context(user_id, inject_evaluation, data, context):
return False return False
if 'evaluation_strategy' not in inject_evaluation: if 'evaluation_strategy' not in inject_evaluation:
return False return False
data_to_validate = get_data_to_validate(user_id, inject_evaluation, data) data_to_validate = await get_data_to_validate(user_id, inject_evaluation, data)
if data_to_validate is None: if data_to_validate is None:
logger.debug('Could not fetch data to validate') logger.debug('Could not fetch data to validate')
return False return False
@ -361,16 +360,16 @@ def inject_checker_router(user_id: int, inject_evaluation: dict, data: dict, con
return False return False
def get_data_to_validate(user_id: int, inject_evaluation: dict, data: dict) -> Union[dict, list, str, None]: async def get_data_to_validate(user_id: int, inject_evaluation: dict, data: dict) -> Union[dict, list, str, None]:
data_to_validate = None data_to_validate = None
if inject_evaluation['evaluation_strategy'] == 'data_filtering': if inject_evaluation['evaluation_strategy'] == 'data_filtering':
event_id = parse_event_id_from_log(data) event_id = parse_event_id_from_log(data)
data_to_validate = fetch_data_for_data_filtering(event_id=event_id) data_to_validate = await fetch_data_for_data_filtering(event_id=event_id)
elif inject_evaluation['evaluation_strategy'] == 'query_mirror': elif inject_evaluation['evaluation_strategy'] == 'query_mirror':
perfomed_query = parse_performed_query_from_log(data) perfomed_query = parse_performed_query_from_log(data)
data_to_validate = fetch_data_for_query_mirror(user_id, inject_evaluation, perfomed_query) data_to_validate = await fetch_data_for_query_mirror(user_id, inject_evaluation, perfomed_query)
elif inject_evaluation['evaluation_strategy'] == 'query_search': elif inject_evaluation['evaluation_strategy'] == 'query_search':
data_to_validate = fetch_data_for_query_search(user_id, inject_evaluation) data_to_validate = await fetch_data_for_query_search(user_id, inject_evaluation)
return data_to_validate return data_to_validate
@ -416,14 +415,14 @@ def parse_performed_query_from_log(data: dict) -> Union[dict, None]:
return None return None
def fetch_data_for_data_filtering(event_id=None) -> Union[None, dict]: async def fetch_data_for_data_filtering(event_id=None) -> Union[None, dict]:
data = None data = None
if event_id is not None: if event_id is not None:
data = misp_api.getEvent(event_id) data = await misp_api.getEvent(event_id)
return data return data
def fetch_data_for_query_mirror(user_id: int, inject_evaluation: dict, perfomed_query: dict) -> Union[None, dict]: async def fetch_data_for_query_mirror(user_id: int, inject_evaluation: dict, perfomed_query: dict) -> Union[None, dict]:
data = None data = None
authkey = db.USER_ID_TO_AUTHKEY_MAPPING[user_id] authkey = db.USER_ID_TO_AUTHKEY_MAPPING[user_id]
if perfomed_query is not None: if perfomed_query is not None:
@ -433,8 +432,8 @@ def fetch_data_for_query_mirror(user_id: int, inject_evaluation: dict, perfomed_
expected_method = query_context['request_method'] expected_method = query_context['request_method']
expected_url = query_context['url'] expected_url = query_context['url']
expected_payload = inject_evaluation['parameters'][0] expected_payload = inject_evaluation['parameters'][0]
expected_data = misp_api.doRestQuery(authkey, expected_method, expected_url, expected_payload) expected_data = await misp_api.doRestQuery(authkey, expected_method, expected_url, expected_payload)
data_to_validate = misp_api.doRestQuery(authkey, perfomed_query['request_method'], perfomed_query['url'], perfomed_query['payload']) data_to_validate = await misp_api.doRestQuery(authkey, perfomed_query['request_method'], perfomed_query['url'], perfomed_query['payload'])
data = { data = {
'expected_data' : expected_data, 'expected_data' : expected_data,
'data_to_validate' : data_to_validate, 'data_to_validate' : data_to_validate,
@ -442,20 +441,20 @@ def fetch_data_for_query_mirror(user_id: int, inject_evaluation: dict, perfomed_
return data return data
def fetch_data_for_query_search(user_id: int, inject_evaluation: dict) -> Union[None, dict]: async def fetch_data_for_query_search(user_id: int, inject_evaluation: dict) -> Union[None, dict]:
authkey = db.USER_ID_TO_AUTHKEY_MAPPING[user_id] authkey = db.USER_ID_TO_AUTHKEY_MAPPING[user_id]
if 'evaluation_context' not in inject_evaluation and 'query_context' not in inject_evaluation['evaluation_context']: if 'evaluation_context' not in inject_evaluation and 'query_context' not in inject_evaluation['evaluation_context']:
return None return None
query_context = inject_evaluation['evaluation_context']['query_context'] query_context = inject_evaluation['evaluation_context']['query_context']
search_method = query_context['request_method'] search_method = query_context['request_method']
search_url = query_context['url'] search_url = query_context['url']
search_payload = inject_evaluation['payload'] search_payload = query_context['payload']
search_data = misp_api.doRestQuery(authkey, search_method, search_url, search_payload) search_data = await misp_api.doRestQuery(authkey, search_method, search_url, search_payload)
return search_data return search_data
@debounce_check_active_tasks(debounce_seconds=2) @debounce_check_active_tasks(debounce_seconds=2)
def check_active_tasks(user_id: int, data: dict, context: dict) -> bool: async def check_active_tasks(user_id: int, data: dict, context: dict) -> bool:
succeeded_once = False succeeded_once = False
available_tasks = get_available_tasks_for_user(user_id) available_tasks = get_available_tasks_for_user(user_id)
for task_uuid in available_tasks: for task_uuid in available_tasks:
@ -463,7 +462,7 @@ def check_active_tasks(user_id: int, data: dict, context: dict) -> bool:
if inject['exercise_uuid'] not in db.SELECTED_EXERCISES: if inject['exercise_uuid'] not in db.SELECTED_EXERCISES:
continue continue
logger.debug(f"[{task_uuid}] :: checking: {inject['name']}") logger.debug(f"[{task_uuid}] :: checking: {inject['name']}")
completed = check_inject(user_id, inject, data, context) completed = await check_inject(user_id, inject, data, context)
if completed: if completed:
succeeded_once = True succeeded_once = True
return succeeded_once return succeeded_once

View file

@ -138,15 +138,15 @@
{ {
"parameters": [ "parameters": [
{ {
".Event.user_id": { ".response[].Event.event_creator_email": {
"comparison": "equals", "comparison": "equals",
"values": [ "values": [
"{{user_id}}" "{{user_email}}"
] ]
} }
}, },
{ {
".Event.info": { ".response[].Event.info": {
"comparison": "contains", "comparison": "contains",
"values": [ "values": [
"event", "event",

View file

@ -17,6 +17,8 @@ def jq_extract(path: str, data: dict, extract_type='first'):
# Replace the substring `{{variable}}` by context[variable] in the provided string # Replace the substring `{{variable}}` by context[variable] in the provided string
def apply_replacement_from_context(string: str, context: dict) -> str: def apply_replacement_from_context(string: str, context: dict) -> str:
replacement_regex = r"{{(\w+)}}" replacement_regex = r"{{(\w+)}}"
if r'{{' not in string and r'}}' not in string:
return string
matches = re.fullmatch(replacement_regex, string, re.MULTILINE) matches = re.fullmatch(replacement_regex, string, re.MULTILINE)
if not matches: if not matches:
return string return string

View file

@ -51,7 +51,7 @@ zsocket.setsockopt_string(zmq.SUBSCRIBE, '')
# Initialize Socket.IO server # Initialize Socket.IO server
# sio = socketio.Server(cors_allowed_origins='*', async_mode='eventlet') # sio = socketio.Server(cors_allowed_origins='*', async_mode='eventlet')
sio = socketio.AsyncServer(cors_allowed_origins='*', async_mode='aiohttp', logger=True, engineio_logger=True) sio = socketio.AsyncServer(cors_allowed_origins='*', async_mode='aiohttp')
app = web.Application() app = web.Application()
sio.attach(app) sio.attach(app)
@ -146,8 +146,8 @@ async def handleMessage(topic, s, message):
user_id = notification_model.get_user_id(data) user_id = notification_model.get_user_id(data)
if user_id is not None: if user_id is not None:
if exercise_model.is_accepted_query(data): if exercise_model.is_accepted_query(data):
context = get_context(user_id, data) context = get_context(topic, user_id, data)
succeeded_once = exercise_model.check_active_tasks(user_id, data, context) succeeded_once = await exercise_model.check_active_tasks(user_id, data, context)
if succeeded_once: if succeeded_once:
await sendRefreshScore() await sendRefreshScore()
@ -157,9 +157,12 @@ async def sendRefreshScore():
await sio.emit('refresh_score') await sio.emit('refresh_score')
def get_context(user_id: int, data: dict) -> dict: def get_context(topic: str, user_id: int, data: dict) -> dict:
context = { context = {
'zmq_topic': topic,
'user_id': user_id, 'user_id': user_id,
'user_email': db.USER_ID_TO_EMAIL_MAPPING.get(user_id, None),
'user_authkey': db.USER_ID_TO_AUTHKEY_MAPPING.get(user_id, None),
} }
if 'Log' in data: if 'Log' in data:
if 'request_is_rest' in data['Log']: if 'request_is_rest' in data['Log']:
@ -208,7 +211,6 @@ async def forward_zmq_to_socketio():
ZMQ_LAST_TIME = time.time() ZMQ_LAST_TIME = time.time()
# await handleMessage(topic, s, m) # await handleMessage(topic, s, m)
except Exception as e: except Exception as e:
print(e)
logger.error('Error handling message %s', e) logger.error('Error handling message %s', e)

View file

@ -2,10 +2,14 @@
import { ref, onMounted } from "vue" import { ref, onMounted } from "vue"
import { socketConnected, zmqLastTime } from "@/socket"; import { socketConnected, zmqLastTime } from "@/socket";
const zmqLastTimeSecond = ref(0) const zmqLastTimeSecond = ref('?')
function refreshLastTime() { function refreshLastTime() {
zmqLastTimeSecond.value = parseInt(((new Date()).getTime() - zmqLastTime.value * 1000) / 1000) if (zmqLastTime.value !== false) {
zmqLastTimeSecond.value = parseInt(((new Date()).getTime() - zmqLastTime.value * 1000) / 1000)
} else {
zmqLastTimeSecond.value = '?'
}
} }
onMounted(() => { onMounted(() => {