# # IRIS Source Code # Copyright (C) 2022 - DFIR IRIS Team # contact@dfir-iris.org # Created by whitekernel - 2022-05-17 # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 3 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # 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. import datetime from pathlib import Path from flask_login import current_user from sqlalchemy import and_ from sqlalchemy import func from app import app from app import db from app.models.models import CaseReceivedFile from app.models.models import DataStoreFile from app.models.models import DataStorePath from app.models.models import Ioc from app.models.models import IocType from app.models.models import Tlp def datastore_get_root(cid): dsp_root = DataStorePath.query.filter( and_(DataStorePath.path_case_id == cid, DataStorePath.path_is_root == True ) ).first() if dsp_root is None: init_ds_tree(cid) dsp_root = DataStorePath.query.filter( and_(DataStorePath.path_case_id == cid, DataStorePath.path_is_root == True ) ).first() return dsp_root def ds_list_tree(cid): dsp_root = datastore_get_root(cid) dsp = DataStorePath.query.filter( and_(DataStorePath.path_case_id == cid, DataStorePath.path_is_root == False ) ).order_by( DataStorePath.path_parent_id ).all() dsf = DataStoreFile.query.filter( DataStoreFile.file_case_id == cid ).all() dsp_root = dsp_root droot_id = f"d-{dsp_root.path_id}" path_tree = { droot_id: { "name": dsp_root.path_name, "type": "directory", "is_root": True, "children": {} } } droot_children = path_tree[droot_id]["children"] files_nodes = {} for dfile in dsf: dfnode = dfile.__dict__ dfnode.pop('_sa_instance_state') dfnode.pop('file_local_name') dfnode['type'] = "file" dnode_id = f"f-{dfile.file_id}" dnode_parent_id = f"d-{dfile.file_parent_id}" if dnode_parent_id == droot_id: droot_children.update({ dnode_id: dfnode }) elif dnode_parent_id in files_nodes: files_nodes[dnode_parent_id].update({ dnode_id: dfnode }) else: files_nodes[dnode_parent_id] = {dnode_id: dfnode} for dpath in dsp: dpath_id = f"d-{dpath.path_id}" dpath_parent_id = f"d-{dpath.path_parent_id}" path_node = { dpath_id: { "name": dpath.path_name, "type": "directory", "children": {} } } path_node_files = files_nodes.get(dpath_id) if path_node_files: path_node[dpath_id]["children"].update(path_node_files) if dpath_parent_id == droot_id: droot_children.update(path_node) else: datastore_iter_tree(dpath_parent_id, path_node, droot_children) return path_tree def init_ds_tree(cid): dsp_root = DataStorePath.query.filter( and_(DataStorePath.path_case_id == cid, DataStorePath.path_is_root == True ) ).all() if dsp_root: return dsp_root dsp_root = DataStorePath() dsp_root.path_name = f'Case {cid}' dsp_root.path_is_root = True dsp_root.path_case_id = cid dsp_root.path_parent_id = 0 db.session.add(dsp_root) db.session.commit() for path in ['Evidences', 'IOCs', 'Images']: dsp_init = DataStorePath() dsp_init.path_parent_id = dsp_root.path_id dsp_init.path_case_id = cid dsp_init.path_name = path dsp_init.path_is_root = False db.session.add(dsp_init) db.session.commit() return dsp_root def datastore_iter_tree(path_parent_id, path_node, tree): for parent_id, node in tree.items(): if parent_id == path_parent_id: node["children"].update(path_node) return tree if isinstance(node, dict): datastore_iter_tree(path_parent_id, path_node, node) return None def datastore_add_child_node(parent_node, folder_name, cid): try: dsp_base = DataStorePath.query.filter( DataStorePath.path_id == parent_node, DataStorePath.path_case_id == cid ).first() except Exception: return True, f'Unable to request datastore for parent node : {parent_node}', None if dsp_base is None: return True, 'Parent node is invalid for this case', None dsp = DataStorePath() dsp.path_case_id = cid dsp.path_name = folder_name dsp.path_parent_id = parent_node dsp.path_is_root = False db.session.add(dsp) db.session.commit() return False, 'Folder added', dsp def datastore_rename_node(parent_node, folder_name, cid): try: dsp_base = DataStorePath.query.filter( DataStorePath.path_id == parent_node, DataStorePath.path_case_id == cid ).first() except Exception: return True, f'Unable to request datastore for parent node : {parent_node}', None if dsp_base is None: return True, 'Parent node is invalid for this case', None dsp_base.path_name = folder_name db.session.commit() return False, 'Folder renamed', dsp_base def datastore_delete_node(node_id, cid): try: dsp_base = DataStorePath.query.filter( DataStorePath.path_id == node_id, DataStorePath.path_case_id == cid ).first() except Exception: return True, f'Unable to request datastore for parent node : {node_id}' if dsp_base is None: return True, 'Parent node is invalid for this case' datastore_iter_deletion(dsp_base, cid) return False, 'Folder and children deleted' def datastore_iter_deletion(dsp, cid): dsp_children = DataStorePath.query.filter( and_(DataStorePath.path_case_id == cid, DataStorePath.path_is_root == False, DataStorePath.path_parent_id == dsp.path_id ) ).all() for dsp_child in dsp_children: datastore_iter_deletion(dsp_child, cid) datastore_delete_files_of_path(dsp.path_id, cid) db.session.delete(dsp) db.session.commit() return None def datastore_delete_files_of_path(node_id, cid): dsf_list = DataStoreFile.query.filter( and_(DataStoreFile.file_parent_id == node_id, DataStoreFile.file_case_id == cid ) ).all() for dsf_list_item in dsf_list: fln = Path(dsf_list_item.file_local_name) if fln.is_file(): fln.unlink(missing_ok=True) db.session.delete(dsf_list_item) db.session.commit() return def datastore_get_path_node(node_id, cid): return DataStorePath.query.filter( DataStorePath.path_id == node_id, DataStorePath.path_case_id == cid ).first() def datastore_get_interactive_path_node(cid): dsp = DataStorePath.query.filter( DataStorePath.path_name == 'Notes Upload', DataStorePath.path_case_id == cid ).first() if not dsp: dsp_root = datastore_get_root(cid) dsp = DataStorePath() dsp.path_case_id = cid dsp.path_parent_id = dsp_root.path_id dsp.path_name = 'Notes Upload' dsp.path_is_root = False db.session.add(dsp) db.session.commit() return dsp def datastore_get_standard_path(datastore_file, cid): root_path = Path(app.config['DATASTORE_PATH']) if datastore_file.file_is_ioc: target_path = root_path / 'IOCs' elif datastore_file.file_is_evidence: target_path = root_path / 'Evidences' else: target_path = root_path / 'Regulars' target_path = target_path / f"case-{cid}" if not target_path.is_dir(): target_path.mkdir(parents=True, exist_ok=True) return target_path / f"dsf-{datastore_file.file_uuid}" def datastore_get_file(file_id, cid): dsf = DataStoreFile.query.filter( DataStoreFile.file_id == file_id, DataStoreFile.file_case_id == cid ).first() return dsf def datastore_delete_file(cur_id, cid): dsf = DataStoreFile.query.filter( DataStoreFile.file_id == cur_id, DataStoreFile.file_case_id == cid ).first() if dsf is None: return True, 'Invalid DS file ID for this case' fln = Path(dsf.file_local_name) if fln.is_file(): fln.unlink(missing_ok=True) db.session.delete(dsf) db.session.commit() return False, f'File {cur_id} deleted' def datastore_add_file_as_ioc(dsf, caseid): ioc = Ioc.query.filter( Ioc.ioc_value == dsf.file_sha256 ).first() ioc_type_id = IocType.query.filter( IocType.type_name == 'sha256' ).first() ioc_tlp_id = Tlp.query.filter( Tlp.tlp_name == 'amber' ).first() if ioc is None: ioc = Ioc() ioc.ioc_value = dsf.file_sha256 ioc.ioc_description = f"SHA256 of {dsf.file_original_name}. Imported from datastore." ioc.ioc_type_id = ioc_type_id.type_id ioc.ioc_tlp_id = ioc_tlp_id.tlp_id ioc.ioc_tags = "datastore" ioc.user_id = current_user.id db.session.add(ioc) db.session.commit() def datastore_add_file_as_evidence(dsf, caseid): crf = CaseReceivedFile.query.filter( CaseReceivedFile.file_hash == dsf.file_sha256 ).first() if crf is None: crf = CaseReceivedFile() crf.file_hash = dsf.file_sha256 crf.file_description = f"Imported from datastore. {dsf.file_description}" crf.case_id = caseid crf.date_added = datetime.datetime.now() crf.filename = dsf.file_original_name crf.file_size = dsf.file_size crf.user_id = current_user.id db.session.add(crf) db.session.commit() return def datastore_get_local_file_path(file_id, caseid): dsf = DataStoreFile.query.filter( DataStoreFile.file_id == file_id, DataStoreFile.file_case_id == caseid ).first() if dsf is None: return True, 'Invalid DS file ID for this case' return False, dsf def datastore_filter_tree(filter_d, caseid): names = filter_d.get('name') storage_names = filter_d.get('storage_name') tags = filter_d.get('tag') descriptions = filter_d.get('description') is_ioc = filter_d.get('is_ioc') is_evidence = filter_d.get('is_evidence') has_password = filter_d.get('has_password') file_id = filter_d.get('id') file_uuid = filter_d.get('uuid') file_sha256 = filter_d.get('sha256') condition = (DataStoreFile.file_case_id == caseid) if file_id: for fid in file_id: if fid: fid = fid.replace('dsf-', '') condition = and_(condition, DataStoreFile.file_id == fid) if file_uuid: for fuid in file_uuid: if fuid: fuid = fuid.replace('dsf-', '') condition = and_(condition, DataStoreFile.file_uuid == fuid) if file_sha256: for fsha in file_sha256: if fsha: condition = and_(condition, func.lower(DataStoreFile.file_sha256) == fsha.lower()) if names: for name in names: condition = and_(condition, DataStoreFile.file_original_name.ilike(f'%{name}%')) if storage_names: for name in storage_names: condition = and_(condition, DataStoreFile.file_local_name.ilike(f'%{name}%')) if tags: for tag in tags: condition = and_(condition, DataStoreFile.file_tags.ilike(f'%{tag}%')) if descriptions: for description in descriptions: condition = and_(condition, DataStoreFile.file_description.ilike(f'%{description}%')) if is_ioc is not None: condition = and_(condition, (DataStoreFile.file_is_ioc == True)) if is_evidence is not None: condition = and_(condition, (DataStoreFile.file_is_evidence == True)) if has_password is not None: condition = and_(condition, (DataStoreFile.file_password != "")) dsp_root = DataStorePath.query.filter( and_(DataStorePath.path_case_id == caseid, DataStorePath.path_is_root == True ) ).first() if dsp_root is None: init_ds_tree(caseid) dsp_root = DataStorePath.query.filter( and_(DataStorePath.path_case_id == caseid, DataStorePath.path_is_root == True ) ).first() dsp = DataStorePath.query.filter( and_(DataStorePath.path_case_id == caseid, DataStorePath.path_is_root == False ) ).order_by( DataStorePath.path_parent_id ).all() try: dsf = DataStoreFile.query.filter( condition ).all() except Exception as e: return None, str(e) dsp_root = dsp_root droot_id = f"d-{dsp_root.path_id}" path_tree = { droot_id: { "name": dsp_root.path_name, "type": "directory", "is_root": True, "children": {} } } droot_children = path_tree[droot_id]["children"] files_nodes = {} for dfile in dsf: dfnode = dfile.__dict__ dfnode.pop('_sa_instance_state') dfnode.pop('file_local_name') dfnode['type'] = "file" dnode_id = f"f-{dfile.file_id}" dnode_parent_id = f"d-{dfile.file_parent_id}" if dnode_parent_id == droot_id: droot_children.update({ dnode_id: dfnode }) elif dnode_parent_id in files_nodes: files_nodes[dnode_parent_id].update({ dnode_id: dfnode }) else: files_nodes[dnode_parent_id] = {dnode_id: dfnode} for dpath in dsp: dpath_id = f"d-{dpath.path_id}" dpath_parent_id = f"d-{dpath.path_parent_id}" path_node = { dpath_id: { "name": dpath.path_name, "type": "directory", "children": {} } } path_node_files = files_nodes.get(dpath_id) if path_node_files: path_node[dpath_id]["children"].update(path_node_files) if dpath_parent_id == droot_id: droot_children.update(path_node) else: datastore_iter_tree(dpath_parent_id, path_node, droot_children) return path_tree, 'Success'