Skip to content

Commit

Permalink
[ADD] Possibility to check alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
whikernel committed Oct 29, 2024
1 parent 0933a3c commit 178df5f
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 111 deletions.
44 changes: 42 additions & 2 deletions source/app/blueprints/case/case_assets_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import app
from app import db
from app.blueprints.case.case_comments import case_comment_update
from app.datamgmt.case.case_assets_db import add_comment_to_asset
from app.datamgmt.case.case_assets_db import add_comment_to_asset, get_raw_assets
from app.datamgmt.case.case_assets_db import create_asset
from app.datamgmt.case.case_assets_db import delete_asset
from app.datamgmt.case.case_assets_db import delete_asset_comment
Expand Down Expand Up @@ -94,6 +94,47 @@ def case_assets(caseid, url_redir):
return render_template("case_assets.html", case=case, form=form)


@case_assets_blueprint.route('/case/assets/filter', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_filter_assets(caseid):
"""
Returns the list of assets from the case.
:return: A JSON object containing the assets of the case, enhanced with assets seen on other cases.
"""

# Get all assets objects from the case and the customer id
ret = {}
assets = CaseAssetsSchema().dump(get_raw_assets(caseid), many=True)
customer_id = get_case_client_id(caseid)

ioc_links_req = get_assets_ioc_links(caseid)

cache_ioc_link = {}
for ioc in ioc_links_req:

if ioc.asset_id not in cache_ioc_link:
cache_ioc_link[ioc.asset_id] = [ioc._asdict()]
else:
cache_ioc_link[ioc.asset_id].append(ioc._asdict())

cases_access = get_user_cases_fast(current_user.id)

for a in assets:
a['ioc_links'] = cache_ioc_link.get(a['asset_id'])

if len(assets) < 300:
# Find similar assets from other cases with the same customer
a['link'] = list(get_similar_assets(
a['asset_name'], a['asset_type_id'], caseid, customer_id, cases_access))
else:
a['link'] = []

ret['assets'] = assets

ret['state'] = get_assets_state(caseid=caseid)

return response_success("", data=ret)

@case_assets_blueprint.route('/case/assets/list', methods=['GET'])
@ac_api_case_requires(CaseAccessLevel.read_only, CaseAccessLevel.full_access)
def case_list_assets(caseid):
Expand All @@ -102,7 +143,6 @@ def case_list_assets(caseid):
:return: A JSON object containing the assets of the case, enhanced with assets seen on other cases.
"""

# Get all assets objects from the case and the customer id
assets = get_assets(caseid)
customer_id = get_case_client_id(caseid)

Expand Down
66 changes: 2 additions & 64 deletions source/app/datamgmt/alerts/alerts_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from collections import defaultdict

from copy import deepcopy

import json
Expand All @@ -26,7 +24,7 @@
from operator import and_
from sqlalchemy import desc, asc, func, tuple_, or_
from sqlalchemy.orm import aliased, make_transient, selectinload
from typing import List, Tuple
from typing import List, Tuple, Dict

import app
from app import db
Expand Down Expand Up @@ -77,35 +75,11 @@ def get_filtered_alerts(
page: int = 1,
per_page: int = 10,
sort: str = 'desc',
current_user_id: int = None,
stack_similar: bool = False
current_user_id: int = None
):
"""
Get a list of alerts that match the given filter conditions
args:
start_date (datetime): The start date of the alert creation time
end_date (datetime): The end date of the alert creation time
title (str): The title of the alert
description (str): The description of the alert
status (str): The status of the alert
severity (str): The severity of the alert
owner (str): The owner of the alert
source (str): The source of the alert
tags (str): The tags of the alert
case_id (int): The case id of the alert
client (int): The client id of the alert
classification (int): The classification id of the alert
alert_ids (int): The alert ids
assets (list): The assets of the alert
iocs (list): The iocs of the alert
resolution_status (int): The resolution status of the alert
page (int): The page number
per_page (int): The number of alerts per page
sort (str): The sort order
current_user_id (int): The ID of the current user
stack_similar (bool): Whether to stack similar alerts based on title, assets, and source
returns:
dict: A dictionary containing the total count, alerts, and pagination information
"""
Expand Down Expand Up @@ -200,42 +174,6 @@ def get_filtered_alerts(
order_func(Alert.alert_source_event_time)
).paginate(page=page, per_page=per_page, error_out=False)

alert_ids = [alert.alert_id for alert in filtered_alerts.items]

# Batch query the AlertSimilarity table for all relevant alerts with specific conditions
similar_alerts = db.session.query(AlertSimilarity).filter(
AlertSimilarity.alert_id.in_(alert_ids),
or_(
and_(AlertSimilarity.similarity_type == 'title_match', AlertSimilarity.matching_asset_id.isnot(None)),
and_(AlertSimilarity.similarity_type == 'title_match', AlertSimilarity.matching_ioc_id.isnot(None)),
and_(AlertSimilarity.matching_asset_id.isnot(None), AlertSimilarity.matching_ioc_id.isnot(None))
)
).all()

# Group similar alerts by alert_id for easier processing
similarity_map = defaultdict(list)
for similar_alert in similar_alerts:
similarity_map[similar_alert.alert_id].append({
'alert_id': similar_alert.similar_alert_id,
'similarity_type': similar_alert.similarity_type,
'matching_asset_id': similar_alert.matching_asset_id,
'matching_ioc_id': similar_alert.matching_ioc_id
})

# Attach aggregated alerts to the alert objects
alerts_dict = []
processed_alerts = set()

for alert in filtered_alerts.items:
if alert.alert_id in processed_alerts:
continue

alert.aggregated_alerts = similarity_map.get(alert.alert_id, [])
alerts_dict.append(alert)
processed_alerts.add(alert.alert_id)
for agg in alert.aggregated_alerts:
processed_alerts.add(agg['alert_id'])

return {
'total': filtered_alerts.total,
'alerts': alert_schema.dump(filtered_alerts, many=True),
Expand Down
8 changes: 8 additions & 0 deletions source/app/datamgmt/case/case_assets_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ def get_assets(caseid):
return assets


def get_raw_assets(caseid):
assets = CaseAssets.query.filter(
CaseAssets.case_id == caseid
).all()

return assets


def get_assets_name(caseid):
assets_names = CaseAssets.query.with_entities(
CaseAssets.asset_name
Expand Down
3 changes: 2 additions & 1 deletion source/app/schema/marshables.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,8 @@ class CaseAssetsSchema(ma.SQLAlchemyAutoSchema):
ioc_links: List[int] = fields.List(fields.Integer, required=False)
asset_enrichment: str = auto_field('asset_enrichment', required=False)
asset_type: AssetTypeSchema = ma.Nested(AssetTypeSchema, required=False)
alerts = fields.Nested('AlertSchema', many=True, exclude=['assets'])
analysis_status = fields.Nested('AnalysisStatusSchema', required=False)

class Meta:
model = CaseAssets
Expand Down Expand Up @@ -2256,7 +2258,6 @@ class AlertSchema(ma.SQLAlchemyAutoSchema):
iocs = ma.Nested(IocSchema, many=True)
assets = ma.Nested(CaseAssetsSchema, many=True)
resolution_status = ma.Nested(AlertResolutionSchema)
aggregated_alerts = ma.Field()

class Meta:
model = Alert
Expand Down
60 changes: 39 additions & 21 deletions source/app/static/assets/js/iris/case.asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ function add_assets() {
function get_case_assets() {
show_loader();

get_request_api('/case/assets/list')
get_request_api('/case/assets/filter')
.done(function (response) {
if (response.status == 'success') {
if (response.data != null) {
Expand All @@ -133,7 +133,17 @@ function get_case_assets() {
Table.clear();
Table.rows.add(jsdata.assets);
Table.columns.adjust().draw();
load_menu_mod_options('asset', Table, delete_asset);
load_menu_mod_options('asset', Table, delete_asset, [{
type: 'option',
title: 'Check Alerts',
multi: false,
iconClass: 'fas fa-bell',
action: function(rows) {
let row = rows[0];
let asset = get_row_value(row);
window.open(`/alerts?alert_assets=${asset}`, '_blank');
}
}]);
$('[data-toggle="popover"]').popover();
set_last_state(jsdata.state);
hide_loader();
Expand Down Expand Up @@ -377,32 +387,40 @@ $(document).ready(function(){
if (row.link.length > 0) {
let has_compro = false;
let datacontent = 'data-content="';
for (let idx in row.link) {
if (row.link[idx]['asset_compromise_status_id'] === 1) {

row.link.forEach(link => {
const caseInfo = `<b><a target='_blank' rel='noopener' href='/case/assets?cid=${link.case_id}&shared=${link.asset_id}'>Observed <sup><i class='fa-solid fa-arrow-up-right-from-square ml-1 mr-1 text-muted'></i></sup></a></b>`;
const caseLink = `<b><a href='/case?cid=${link.case_id}'>case #${link.case_id} <sup><i class='fa-solid fa-arrow-up-right-from-square ml-1 mr-1 text-muted'></i></sup></a></b>`;
const date = link.case_open_date.replace('00:00:00 GMT', '');

if (link.asset_compromise_status_id === 1) {
has_compro = true;
datacontent += `<b><a target='_blank' rel='noopener' href='/case/assets?cid=${row.link[idx]['case_id']}&shared=${row.link[idx]['asset_id']}'>Observed <sup><i class='fa-solid fa-arrow-up-right-from-square ml-1 mr-1 text-muted'></i></sup></a></b> as <b class='text-danger'>compromised</b><br/> on <b><a href='/case?cid=${row.link[idx]['case_id']}'>case #${row.link[idx]['case_id']} <sup><i class='fa-solid fa-arrow-up-right-from-square ml-1 mr-1 text-muted'></i></sup></a></a></b> (${row.link[idx]['case_open_date'].replace('00:00:00 GMT', '')}) for the same customer.<br/><br/>`;
datacontent += `${caseInfo} as <b class='text-danger'>compromised</b><br/> on ${caseLink} (${date}) for the same customer.<br/><br/>`;
} else {

datacontent += `<b><a target='_blank' rel='noopener' href='/case/assets?cid=${row.link[idx]['case_id']}&shared=${row.link[idx]['asset_id']}'>Observed <sup><i class='fa-solid fa-arrow-up-right-from-square ml-1 mr-1 text-muted'></i></sup></a></b> as <b class='text-success'>not compromised</b><br/> on <b><a href='/case?cid=${row.link[idx]['case_id']}'>case #${row.link[idx]['case_id']} <sup><i class='fa-solid fa-arrow-up-right-from-square ml-1 mr-1 text-muted'></i></sup></a></a></b> (${row.link[idx]['case_open_date'].replace('00:00:00 GMT', '')}) for the same customer.<br/><br/>`;
datacontent += `${caseInfo} as <b class='text-success'>not compromised</b><br/> on ${caseLink} (${date}) for the same customer.<br/><br/>`;
}
}
if (has_compro) {
compro += `<a tabindex="0" class="fas fa-meteor ml-2 text-danger" style="cursor: pointer;" data-html="true"
data-toggle="popover" data-trigger="focus" title="Observed in previous case" `;
} else {
compro += `<a tabindex="0" class="fas fa-info-circle ml-2 text-success" style="cursor: pointer;" data-html="true"
data-toggle="popover" data-trigger="focus" title="Observed in previous case" `;
}
});

compro += `<i tabindex="0" class="fas ${has_compro ? 'fa-meteor text-danger' : 'fa-info-circle text-success'} ml-2" style="cursor: pointer;" data-html="true" data-toggle="popover" data-trigger="focus" title="Observed in previous case" ${datacontent}"></i>`;
}

if (row.alerts.length > 0) {
let alerts_content = "";

row.alerts.forEach(alert => {
alerts_content += `<i tabindex="0" class="fas fa-bell text-warning mr-2"></i><a href=\"/alerts?alert_ids=${alert.alert_id}&page=1&per_page=1&sort=desc\" target="_blank" rel="noopener">#${alert.alert_id} - ${alert.alert_title.replace(/'/g, "&#39;").replace(/"/g, "&quot;")}</a><br/>`;
} );
alerts_content += `<i tabindex="0" class="fas fa-external-link-square mr-2"></i><a href=\"/alerts?alert_assets=${data}" target="_blank" rel="noopener">More..</a>`;


compro += datacontent;
compro += '"></i>';
compro += `<i tabindex="0" class="fas fa-bell text-warning ml-2" style="cursor: pointer;" data-html="true" data-toggle="popover" data-trigger="focus" title="Alerts" data-content='${alerts_content}'></i>`;
}

let img = $('<img>')
.addClass('mr-2')
.css({width: '1.5em', height: '1.5em'})
.attr('src', '/static/assets/img/graph/' + (row['asset_compromise_status_id'] == 1 ? row['asset_icon_compromised'] : row['asset_icon_not_compromised']))
.attr('title', row['asset_type']);
.attr('src', '/static/assets/img/graph/' + (row['asset_compromise_status_id'] == 1 ? row['asset_type']['asset_icon_compromised'] : row['asset_type']['asset_icon_not_compromised']))
.attr('title', row['asset_type']['asset_name']);

let link = $('<a>')
.attr('href', 'javascript:void(0);')
Expand All @@ -419,7 +437,7 @@ $(document).ready(function(){
}
},
{
"data": "asset_type",
"data": "asset_type.asset_name",
"render": function (data, type, row, meta) {
if (type === 'display') { data = sanitizeHTML(data);}
return data;
Expand Down Expand Up @@ -483,7 +501,7 @@ $(document).ready(function(){
"data": "analysis_status",
"render": function(data, type, row, meta) {
if (type === 'display') {
data = sanitizeHTML(data);
data = sanitizeHTML(data['name']);
if (data == 'To be done') {
flag = 'danger';
} else if (data == 'Started') {
Expand Down
Loading

0 comments on commit 178df5f

Please sign in to comment.