FetchIndicatorsFromFile

Fetches indicators from a file. Supports TXT, XLS, XLSX, CSV, DOC and DOCX file types.

python · Common Scripts

Source

import csv
import re
import traceback

import demistomock as demisto
import tldextract
import xlrd
from CommonServerPython import *

from CommonServerUserPython import *


def csv_file_to_indicator_list(file_path, col_num, starting_row, auto_detect, default_type, type_col, limit, offset):
    indicator_list = []

    # TODO: add run on all columns functionality

    line_index = 0
    with open(file_path) as csv_file:
        # csv reader can fail when encountering a NULL byte (\0) - so we go through the file and take out the NUL bytes.
        file_reader = csv.reader(line.replace("\0", "") for line in csv_file)
        for row in file_reader:
            if line_index >= starting_row + offset and len(row) != 0:
                indicator = row[col_num]

                indicator_type = get_indicator_type(
                    indicator, auto_detect, default_type, file_type="csv", type_col=type_col, csv_row=row
                )

                if indicator_type is None:
                    continue

                indicator_list.append({"type": indicator_type, "value": indicator})

            line_index = line_index + 1

            if limit and len(indicator_list) == int(str(limit)):
                break

    return indicator_list


def xls_file_to_indicator_list(file_path, sheet_name, col_num, starting_row, auto_detect, default_type, type_col, limit, offset):
    indicator_list = []

    # TODO: add run on all columns functionality

    # Ensure that the has_iter will not be reseted after opening the workbook.
    xlrd.xlsx.ensure_elementtree_imported(False, None)
    xlrd.xlsx.Element_has_iter = True
    xl_woorkbook = xlrd.open_workbook(file_path)
    if sheet_name and sheet_name != "None":
        xl_sheet = xl_woorkbook.sheet_by_name(sheet_name)

    else:
        xl_sheet = xl_woorkbook.sheet_by_index(0)

    for row_index in range(starting_row + offset, xl_sheet.nrows):
        indicator = xl_sheet.cell(row_index, col_num).value

        indicator_type = get_indicator_type(
            indicator, auto_detect, default_type, file_type="xls", type_col=type_col, xl_sheet=xl_sheet, xl_row_index=row_index
        )

        if indicator_type is None:
            continue

        indicator_list.append({"type": indicator_type, "value": indicator})

        if limit and len(indicator_list) == int(str(limit)):
            break

    return indicator_list


def txt_file_to_indicator_list(file_path, auto_detect, default_type, limit, offset):
    with open(file_path) as fp:
        file_data = fp.read()

    indicator_list = []

    raw_splitted_data = re.split(r"\s|\n|\t|\"|\'|\,|\0", file_data)
    indicator_index = 0

    for indicator in raw_splitted_data:
        # drop punctuation
        if len(indicator) > 1:
            while indicator[-1] in '.,?:;\\)}]/!\n\t\0"' and len(indicator) > 1:
                indicator = indicator[:-1]

            while indicator[0] in '.,({[\n\t"' and len(indicator) > 1:
                indicator = indicator[1:]

            indicator_type = get_indicator_type(indicator, auto_detect, default_type, file_type="text")

            # indicator not recognized skip the word
            if indicator_type is None:
                continue

            if indicator_type is not None and indicator_index < offset:
                indicator_index = indicator_index + 1
                continue

            indicator_list.append({"type": indicator_type, "value": indicator})

        if limit and len(indicator_list) == int(str(limit)):
            break

    return indicator_list


def get_indicator_type(
    indicator_value, auto_detect, default_type, file_type, type_col=None, xl_sheet=None, xl_row_index=0, csv_row=None
):
    """Returns the indicator type for the given file type.

    Args:
        indicator_value (str): the indicator value
        auto_detect (bool): whether or not to auto_detect the type
        default_type (Any): the default type of the indicator (could be None or str)
        file_type (str): 'text', 'xls' or 'csv'
        type_col (Any): the column from which to fetch the indicator type in xls or csv files (could be None or int)
        xl_sheet (Any): the xls sheet from which to fetch the indicator type (could be None or ~xlrd.sheet.Sheet)
        xl_row_index (Any): the row number in the xls sheet from which to fetch the indicator type (could be None or int)
        csv_row (Any): the csv row from which to fetch the indicator type (could be None or list)

    Returns:
        Any. returns None if indicator is not recognized in text file
        or the indicator is not recognized and no default type given.
        Otherwise will return a string indicating the indicator type
    """
    indicator_type = detect_type(indicator_value)

    # indicator not recognized skip the word in text file
    if indicator_type is None and file_type == "text":
        return None

    if not auto_detect:
        indicator_type = default_type

    if file_type != "text":
        if type_col is not None and file_type == "xls":
            indicator_type = xl_sheet.cell(xl_row_index, int(type_col) - 1).value

        elif type_col is not None and file_type == "csv":
            indicator_type = csv_row[int(type_col) - 1]

        # indicator not recognized in non text file
        if indicator_type is None:
            # no default value given
            if default_type is None:
                return None
            else:
                # default value given
                indicator_type = default_type

    return indicator_type


def detect_type(indicator):
    """Infer the type of the indicator.
    Args:
        indicator(str): The indicator whose type we want to check.
    Returns:
        str. The type of the indicator.
    """
    if re.match(sha256Regex, indicator) or re.match(md5Regex, indicator) or re.match(sha1Regex, indicator):
        return FeedIndicatorType.File

    if re.match(ipv4cidrRegex, indicator):
        return FeedIndicatorType.CIDR

    if re.match(ipv6cidrRegex, indicator):
        return FeedIndicatorType.IPv6CIDR

    if re.match(ipv4Regex, indicator):
        return FeedIndicatorType.IP

    if re.match(ipv6Regex, indicator):
        return FeedIndicatorType.IPv6

    if re.match(urlRegex, indicator):
        return FeedIndicatorType.URL

    if re.match(emailRegex, indicator):
        return FeedIndicatorType.Email

    try:
        # we use TLDExtract class to fetch all existing domain suffixes from the bellow mentioned file:
        # https://raw.githubusercontent.com/publicsuffix/list/master/public_suffix_list.dat
        # the suffix_list_urls=None is used to not make http calls using the extraction - avoiding SSL errors
        if (
            tldextract.TLDExtract(
                cache_dir="https://raw.githubusercontent.com/publicsuffix/list/master/public_suffix_list.dat",
                suffix_list_urls=(),
            )
            .__call__(indicator)
            .suffix
        ):
            if "*" in indicator:
                return FeedIndicatorType.DomainGlob
            return FeedIndicatorType.Domain
    except Exception:
        pass

    return None


def fetch_indicators_from_file(args):
    file = demisto.getFilePath(args.get("entry_id"))
    file_path = file["path"]
    file_name = file["name"]
    auto_detect = args.get("auto_detect") == "True"
    default_type = args.get("default_type")
    limit = args.get("limit")

    # offset - refers to the indicator list itself -
    # lets say you have a list of 500 and you put a limit of 100 on your output -
    # you can get the next 100 by putting an offset of 100.
    offset = int(str(args.get("offset"))) if args.get("offset") else 0

    # the below params are for Excel type files only.
    sheet_name = args.get("sheet_name")
    indicator_col_num = args.get("indicator_column_number")
    indicator_type_col_num = args.get("indicator_type_column_number")

    # starting_row is for excel files -
    # from which row should I start reading the indicators, it is used to avoid table headers.
    starting_row = args.get("starting_row")

    if file_name.endswith(("xls", "xlsx")):
        indicator_list = xls_file_to_indicator_list(
            file_path,
            sheet_name,
            int(indicator_col_num) - 1,
            int(starting_row) - 1,
            auto_detect,
            default_type,
            indicator_type_col_num,
            limit,
            offset,
        )

    elif file_name.endswith("csv"):
        indicator_list = csv_file_to_indicator_list(
            file_path,
            int(indicator_col_num) - 1,
            int(starting_row) - 1,
            auto_detect,
            default_type,
            indicator_type_col_num,
            limit,
            offset,
        )

    else:
        indicator_list = txt_file_to_indicator_list(file_path, auto_detect, default_type, limit, offset)

    human_readable = tableToMarkdown(f"Indicators from {file_name}:", indicator_list, headers=["value", "type"], removeNull=True)

    # Create indicators in demisto
    errors = []
    domains = []
    for indicator in indicator_list:
        res = demisto.executeCommand("createNewIndicator", indicator)
        if is_error(res[0]):
            errors.append("Error creating indicator - {}".format(res[0]["Contents"]))

        domain_obj = {"Name": indicator.get("value")}
        if indicator.get("type") == FeedIndicatorType.Domain and domain_obj not in domains:
            domains.append(domain_obj)
    if errors:
        return_error(json.dumps(errors, indent=4))
    domain_context = {outputPaths["domain"]: domains} if domains else None
    return human_readable, domain_context, indicator_list


def main():
    try:
        return_outputs(*fetch_indicators_from_file(demisto.args()))
    except Exception as ex:
        return_error(f"Failed to execute Fetch Indicators From File. Error: {ex!s}", error=traceback.format_exc())


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()

README

Fetches indicators from a file.

Supported File Types

  • TXT
  • XLS, XLSX
  • CSV
  • DOC, DOCX

If an Excel file is supplied (XLS, XLSX, CSV), you need to specify the column_number argument, which defines the column to fetch from.

Script Data


Name Description
Script Type python3
Tags indicators
Cortex XSOAR Version 5.5.0

Inputs


Argument Name Description
entry_id The file entry_id from which to fetch the indicators.
auto_detect Whether to auto-detect the indicator type from the file.
default_type Sets a default indicator type.
limit The maximum number of indicators to fetch. If this argument is not specified, will parse the entire file.
offset The index for the first indicator to fetch.
indicator_column_number Only for spreadsheet files. The column number in the spreadsheet that contains the indicators. The first column number is 1. If this argument is not specified, will use at the first column.
sheet_name Only for spreadsheet files. The name of the Excel sheet to fetch indicators from. If this argument is not specified, will fetch from the first sheet of the workbook.
indicator_type_column_number Only for spreadsheet files. The column number in the spreadsheet that contains the indicator types. The first column number is 1.
starting_row Only for spreadsheet files. The starting row of the spreadsheet to fetch from. The first row is 1.

Automation Example

!FetchIndicatorsFromFile auto_detect=True entry_id={entry_id}

Human Readable Output


Indicators from indicator.csv:

value type
xsoar.com Domain
8.8.8.8 IP
8.8.8.8/12 CIDR