Ejemplo n.º 1
0
def ValhallaReporter(values):
    vallaha_val = []
    for usrInput in values:
        valFramework = copy.deepcopy(empty)
        chk_256 = list(iocextract.extract_sha256_hashes(usrInput))
        if chk_256:
            usrInput = chk_256[0]
            v = ValhallaAPI(api_key=valhallaKey)
            response = v.get_hash_info(hash=usrInput)
            if response.get('status') == 'empty':
                pass
            else:
                dataReport = response.get('results')[0]
                if len(dataReport) > 0:
                    valFramework.update({'Action On': usrInput})
                    valFramework.update(
                        {'Positives': dataReport.get('positives')})
                    valFramework.update(
                        {'Rule Name': dataReport.get('rulename')})
                    valFramework.update({'Tags': dataReport.get('tags')})
                    valFramework.update(
                        {'Timestamp': dataReport.get('timestamp')})
                    valFramework.update({'Total': dataReport.get('total')})
                    valFramework.update({
                        'Score':
                        str(dataReport.get('positives')) + '/' +
                        str(dataReport.get('total'))
                    })

                    vallaha_val.append({'Vallaha': valFramework})

    return vallaha_val
Ejemplo n.º 2
0
def test_quote():
    """
    Tests the quote page to check if the service can be access
    :return:
    """
    v = ValhallaAPI(api_key="")
    assert "brave shall live forever" in v.get_quote()
Ejemplo n.º 3
0
def test_status():
    """
    Retrieves the API status
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    status = v.get_status()
    assert status["status"] == "green"
Ejemplo n.º 4
0
def test_demo_rules_json():
    """
    Retrieves the demo rules from the rule feed
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    rules_response = v.get_rules_json()
    assert len(rules_response['rules']) > 0
Ejemplo n.º 5
0
def test_invalid_key():
    """
    Trying to retrieve rules with an invalid key
    :return:
    """
    v = ValhallaAPI(api_key=INVALID_KEY)
    with pytest.raises(Exception):
        v.get_rules_text()
Ejemplo n.º 6
0
def test_get_rule_info():
    """
    Trying to retrieve info for a certain rule (the only one accessible with DEMO key)
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    response = v.get_rule_info('Casing_Anomaly_ByPass')
    assert len(response) > 1
    assert len(response['rule_matches']) > 0
Ejemplo n.º 7
0
def test_demo_rules_text():
    """
    Retrieves the demo rules from the rule feed
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    response = v.get_rules_text()
    assert RULES_TEXT in response
    assert len(response) > 500
Ejemplo n.º 8
0
def test_get_hash_info():
    """
    Trying to retrieve info for a certain hash (the only one accessible with DEMO key)
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    response = v.get_hash_info(
        '8a883a74702f83a273e6c292c672f1144fd1cce8ee126cd90c95131e870744af')
    assert len(response) > 1
    assert len(response['results']) > 0
Ejemplo n.º 9
0
def test_invalid_key():
    """
    Trying to retrieve rules with an invalid key
    :return:
    """
    v = ValhallaAPI(api_key=INVALID_KEY)
    response = v.get_rules_text()
    assert 'status' in response
    assert 'error' in response
    assert len(response) < 500
Ejemplo n.º 10
0
def test_demo_rules_search_limited():
    """
    Retrieves the demo rules from the rule feed with custom expressions
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    rules_response1 = v.get_rules_json()
    rules_response2 = v.get_rules_json(search="Mimikatz")
    assert len(rules_response1['rules']) > 1
    assert len(rules_response2['rules']) > 1
    assert len(rules_response1['rules']) > len(rules_response2['rules'])
Ejemplo n.º 11
0
def test_demo_rules_tag_limited():
    """
    Retrieves the demo rules from the rule feed with custom expressions
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    rules_response1 = v.get_rules_json()
    rules_response2 = v.get_rules_json(tags=['APT'])
    assert len(rules_response1['rules']) > 0
    assert len(rules_response2['rules']) > 0
    assert len(rules_response1['rules']) > len(rules_response2['rules'])
Ejemplo n.º 12
0
def test_demo_rule_info():
    """
    Retrieves the demo rule info
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    rules_response1 = v.get_rule_info(RULE_INFO_DISALLOWED)
    assert 'rule_matches' not in rules_response1
    rules_response2 = v.get_rule_info(RULE_INFO_TEST)
    assert 'rule_matches' in rules_response2
    assert len(rules_response2['rule_matches']) > 0
Ejemplo n.º 13
0
def test_subscription():
    """
    Retrieves the subscription status of the current user
    :return:
    """
    v = ValhallaAPI()
    response = v.get_subscription()
    print(response)
    assert len(response) == 5
    assert response["subscription"] == "limited"
    assert response["tags"] == ['DEMO']
Ejemplo n.º 14
0
def test_demo_rules_product_limited():
    """
    Retrieves the demo rules from the rule feed with a product set
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    rules_response = v.get_rules_json()
    rules_response_limited = v.get_rules_json(product="DummyTest")
    assert len(rules_response['rules']) > 0
    assert len(rules_response['rules']) > len(rules_response_limited['rules'])
    rules_response_limited2 = v.get_rules_json(product="CarbonBlack")
    assert len(rules_response_limited2['rules']) > 0
Ejemplo n.º 15
0
def test_demo_rules_custom_limited():
    """
    Retrieves the demo rules from the rule feed with custom expressions
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    rules_response1 = v.get_rules_json(product="DummyTest")
    rules_response2 = v.get_rules_json(max_version="3.2.0", modules=['pe'])
    rules_response3 = v.get_rules_json(max_version="3.2.0", modules=['pe'], with_crypto=False)
    assert len(rules_response1['rules']) > 0
    assert len(rules_response2['rules']) > 0
    assert len(rules_response1['rules']) < len(rules_response2['rules'])
    assert len(rules_response3['rules']) < len(rules_response2['rules'])
Ejemplo n.º 16
0
def test_demo_rules_combo_limited():
    """
    Retrieves the demo rules from the rule feed with custom expressions
    :return:
    """
    v = ValhallaAPI(api_key=DEMO_KEY)
    rules_response1 = v.get_rules_json()
    rules_response2 = v.get_rules_json(score=60)
    rules_response3 = v.get_rules_json(tags=['SUSP'], score=60)
    assert len(rules_response1['rules']) > 1
    assert len(rules_response2['rules']) > 1
    assert len(rules_response3['rules']) > 1
    assert len(rules_response1['rules']) > len(rules_response2['rules'])
    assert len(rules_response2['rules']) > len(rules_response3['rules'])
Ejemplo n.º 17
0
    def __init__(self):
        # Instantiate the connector helper from config
        config_file_path = os.path.dirname(
            os.path.abspath(__file__)) + "/../config.yml"
        config = (yaml.load(open(config_file_path), Loader=yaml.FullLoader)
                  if os.path.isfile(config_file_path) else {})
        # Extra config
        self.confidence_level = get_config_variable(
            "CONNECTOR_CONFIDENCE_LEVEL",
            ["connector", "confidence_level"],
            config,
            isNumber=True,
        )
        self.update_existing_data = get_config_variable(
            "CONNECTOR_UPDATE_EXISTING_DATA",
            ["connector", "update_existing_data"],
            config,
        )
        self.API_KEY = get_config_variable("VALHALLA_API_KEY",
                                           ["valhalla", "api_key"], config)
        self.INTERVAL_SEC = get_config_variable(
            "VALHALLA_INTERVAL_SEC",
            ["valhalla", "interval_sec"],
            config,
            isNumber=True,
        )

        self.helper = OpenCTIConnectorHelper(config)
        self.helper.log_info(f"loaded valhalla config: {config}")

        # If we run without API key we can assume all data is TLP:WHITE else we
        # default to TLP:AMBER to be safe.
        if self.API_KEY == "" or self.API_KEY is None:
            self.default_marking = self.helper.api.marking_definition.read(
                id=TLP_WHITE["id"])
            self.valhalla_client = ValhallaAPI()
        else:
            self.default_marking = self.helper.api.marking_definition.read(
                id=TLP_AMBER["id"])
            self.valhalla_client = ValhallaAPI(api_key=self.API_KEY)

        self.knowledge_importer = KnowledgeImporter(
            self.helper,
            self.confidence_level,
            self.update_existing_data,
            self.default_marking,
            self.valhalla_client,
        )
Ejemplo n.º 18
0
def main():
    """
    Main Function (used as entry point)
    :return:
    """
    # Parse Arguments
    parser = argparse.ArgumentParser(description='Valhalla-CLI')
    parser.add_argument('-k',
                        help='API KEY',
                        metavar='apikey',
                        default=ValhallaAPI.DEMO_KEY)
    parser.add_argument('-c',
                        help='Config file (see README for details)',
                        metavar='config-file',
                        default=os.path.join(str(Path.home()), ".valhalla"))
    parser.add_argument('-o',
                        help='output file',
                        metavar='output-file',
                        default=ValhallaAPI.DEFAULT_OUTPUT_FILE)
    parser.add_argument('--check',
                        action='store_true',
                        default=False,
                        help='Check subscription info and total rule count')
    parser.add_argument('--debug',
                        action='store_true',
                        default=False,
                        help='Debug output')

    group_proxy = parser.add_argument_group(
        '=======================================================================\nProxy'
    )
    group_proxy.add_argument('-p',
                             help='proxy URL (e.g. https://my.proxy.net:8080)',
                             metavar='proxy-url',
                             default='')
    group_proxy.add_argument('-pu',
                             help='proxy user',
                             metavar='proxy-user',
                             default='')
    group_proxy.add_argument('-pp',
                             help='proxy password',
                             metavar='proxy-pass',
                             default='')

    group_filter = parser.add_argument_group(
        '=======================================================================\nFilter'
    )
    group_filter.add_argument('-fp',
                              help='filter product (valid products are: %s)' %
                              ", ".join(ValhallaAPI.PRODUCT_IDENTIFIER),
                              metavar='product',
                              default='')
    group_filter.add_argument(
        '-fv',
        help='get rules that support the given YARA version and lower',
        metavar='yara-version',
        default='')
    group_filter.add_argument(
        '-fm',
        help=
        'set a list of modules that your product supports (e.g. "-fm pe hash") '
        '(setting no modules means that all modules are supported by your product)',
        action='append',
        nargs='+',
        metavar='modules')
    group_filter.add_argument(
        '-ft',
        help='set a list of tags to receive (e.g. "-ft APT MAL")',
        action='append',
        nargs='+',
        metavar='tags')
    group_filter.add_argument(
        '-fs',
        help='minimum score of rules to retrieve (e.g. "-fs 75")',
        metavar='score',
        default=0)
    group_filter.add_argument(
        '-fq',
        help=
        'get only rules that match a certain keyword in name or description '
        '(e.g. "-fq Mimikatz")',
        metavar='query',
        default='')
    group_filter.add_argument(
        '--nocrypto',
        help='filter all rules that require YARA to be compiled with crypto '
        'support (OpenSSL)',
        action='store_false',
        default=True)

    group_proxy = parser.add_argument_group(
        '=======================================================================\nLookups'
    )
    group_proxy.add_argument(
        '-lr',
        help='Lookup a certain rule (returns matching samples)',
        metavar='lookup-rule',
        default='')
    group_proxy.add_argument(
        '-lh',
        help='Lookup a certain sample hash (sha256) (returns matching rules)',
        metavar='lookup-hash',
        default='')
    group_proxy.add_argument(
        '-lk',
        help='Lookup rules with a certain keyword (returns matching rules)',
        metavar='lookup-keyword',
        default='')
    group_proxy.add_argument(
        '-lkm',
        help=
        'Lookup hashes of samples on which rules have matches that contain a certain '
        'keyword (returns matching sample hashes)',
        metavar='lookup-keyword',
        default='')
    group_proxy.add_argument('-lo',
                             help='Output file for the lookup output',
                             metavar='lookup-output',
                             default='')

    args = parser.parse_args()

    print(" ")
    print("===========================================================")
    print("   _   __     ____        ____         _______   ____ ")
    print("  | | / /__ _/ / /  ___ _/ / /__ _____/ ___/ /  /  _/ ")
    print("  | |/ / _ `/ / _ \\/ _ `/ / / _ `/___/ /__/ /___/ /   ")
    print("  |___/\\_,_/_/_//_/\\_,_/_/_/\\_,_/    \\___/____/___/   ")
    print("   Ver. %s, Florian Roth, 2021                        " %
          __version__)
    print(" ")
    print("===========================================================")
    print(" ")

    # Logging
    logFormatter = logging.Formatter("[%(levelname)-5.5s] %(message)s")
    logFormatterRemote = logging.Formatter(
        "{0} [%(levelname)-5.5s] %(message)s".format(platform.uname()[1]))
    Log = logging.getLogger(__name__)
    Log.setLevel(logging.INFO)
    # Console Handler
    consoleHandler = logging.StreamHandler()
    consoleHandler.setFormatter(logFormatter)
    Log.addHandler(consoleHandler)

    # API Key
    apikey = args.k
    Log.info(
        "Trying to read Valhalla config file at '%s' (set manually with -c)" %
        args.c)
    if os.path.exists(args.c):
        Log.debug("Config file found at '%s'" % args.c)
        config = configparser.ConfigParser()
        config.read(args.c)
        if 'DEFAULT' not in config:
            Log.error(
                "section [DEFAULT] missing in config file - skipping this config"
            )
        else:
            apikey = config['DEFAULT']['APIKEY']
            Log.info("Successfully read config file")
    else:
        Log.info(
            "No config file found, will rely on API KEY passed via cmd line arguments (-k)"
        )

    # Check key
    if apikey == ValhallaAPI.DEMO_KEY:
        Log.warning(
            "You are using the DEMO API key and will only retrieve the reduced open source signature set"
        )
        Log.warning(
            "Set your private API key with '-k APIKEY' to get the rule sets that your have subscribed"
        )

    # Create the ValhallaAPI object
    v = ValhallaAPI(api_key=apikey)

    # Subscription check
    if args.check:
        status = v.get_subscription()
        if 'active' in status:
            if status['active']:
                Log.info("Account is active: %s" % status)
                sys.exit(0)
            else:
                Log.error("Account is inactive: %s" % status)
                sys.exit(1)
        else:
            Log.error("Error: %s" % status['message'])
            sys.exit(1)

    # Proxy
    if args.p:
        Log.info("Setting proxy URL: %s USER: %s PASS: (hidden)" %
                 (args.p, args.pu))
        if args.p.startswith("http:"):
            Log.warning(
                "URL starts with http instead of https - you should use a TLS encrypted connection"
            )
        v.set_proxy(args.p, args.pu, args.pp)

    # Default: Get all rules that the set API key is subscribed to
    # prepare some variables
    modules = []
    if args.fm:
        modules = args.fm[0]
    tags = []
    if args.ft:
        tags = args.ft[0]

    # Lookups
    if args.lr or args.lh or args.lk or args.lkm:
        # Rule Lookup
        if args.lr != "":
            r = v.get_rule_info(args.lr)
        # Hash Lookup
        if args.lh != "":
            r = v.get_hash_info(args.lh)
        # Keyword to Rules Lookup
        if args.lk != "":
            r = v.get_keyword_rules(args.lk)
        # Keyword to Rule Matches Lookup
        if args.lkm != "":
            r = v.get_keyword_rule_matches(args.lkm)

        # Write them to an output file
        if args.lo:
            with open(args.lo, 'w') as fh:
                fh.write(json.dumps(r, indent=4, sort_keys=True))
        else:
            # Show results
            print(json.dumps(r, indent=4, sort_keys=True))
        sys.exit(0)

    # Score warning
    if args.fs == 0:
        Log.warning(
            "Note that an unfiltered set (-fs 0) contains low scoring rules used for threat hunting purposes"
        )

    # Info output
    Log.info(
        "Retrieving rules with params PRODUCT: %s MAX_VERSION: %s MODULES: %s WITH_CRYPTO: %s TAGS: %s "
        "SCORE: %s QUERY: %s" %
        (args.fp, args.fv, ", ".join(modules), str(
            args.nocrypto), ", ".join(tags), str(args.fs), args.fq))

    # Retrieve rules
    try:
        response = v.get_rules_text(
            product=args.fp,
            max_version=args.fv,
            modules=modules,
            with_crypto=args.nocrypto,
            tags=tags,
            score=int(args.fs),
            search=args.fq,
        )
    except UnknownProductError as e:
        Log.error("Unknown product identifier - please use one of these: %s",
                  ", ".join(ValhallaAPI.PRODUCT_IDENTIFIER))
        sys.exit(1)
    except ApiError as e:
        Log.error(e.message)
        sys.exit(1)

    # Response information
    Log.info("Number of retrieved rules: %d" % v.last_retrieved_rules_count)

    # Output
    output_file = args.o
    # Tanium accepts only the ".yara" extension for imports
    if args.fp == "Tanium" and output_file == "valhalla-rules.yar":
        output_file = "valhalla-rules.yara"
    # Write to the output file
    Log.info("Writing retrieved rules into: %s" % output_file)
    with open(output_file, 'w') as fh:
        fh.write(response)
Ejemplo n.º 19
0
class Valhalla:
    """OpenCTI valhalla main class"""

    _DEMO_API_KEY = "1111111111111111111111111111111111111111111111111111111111111111"
    _STATE_LAST_RUN = "last_run"
    _VALHALLA_LAST_VERSION = "valhalla_last_version"

    def __init__(self):
        # Instantiate the connector helper from config
        config_file_path = os.path.dirname(
            os.path.abspath(__file__)) + "/../config.yml"
        config = (yaml.load(open(config_file_path), Loader=yaml.FullLoader)
                  if os.path.isfile(config_file_path) else {})
        # Extra config
        self.confidence_level = get_config_variable(
            "CONNECTOR_CONFIDENCE_LEVEL",
            ["connector", "confidence_level"],
            config,
            isNumber=True,
        )
        self.update_existing_data = get_config_variable(
            "CONNECTOR_UPDATE_EXISTING_DATA",
            ["connector", "update_existing_data"],
            config,
        )
        self.API_KEY = get_config_variable("VALHALLA_API_KEY",
                                           ["valhalla", "api_key"], config)
        self.INTERVAL_SEC = get_config_variable(
            "VALHALLA_INTERVAL_SEC",
            ["valhalla", "interval_sec"],
            config,
            isNumber=True,
        )

        self.helper = OpenCTIConnectorHelper(config)
        self.helper.log_info(f"loaded valhalla config: {config}")

        # If we run without API key we can assume all data is TLP:WHITE else we
        # default to TLP:AMBER to be safe.
        if self.API_KEY == "" or self.API_KEY is None:
            self.default_marking = self.helper.api.marking_definition.read(
                id=TLP_WHITE["id"])
            self.valhalla_client = ValhallaAPI()
        else:
            self.default_marking = self.helper.api.marking_definition.read(
                id=TLP_AMBER["id"])
            self.valhalla_client = ValhallaAPI(api_key=self.API_KEY)

        self.knowledge_importer = KnowledgeImporter(
            self.helper,
            self.confidence_level,
            self.update_existing_data,
            self.default_marking,
            self.valhalla_client,
        )

    def run(self):
        self.helper.log_info("starting valhalla connector...")
        while True:
            try:
                status_data = self.valhalla_client.get_status()
                api_status = Status.parse_obj(status_data)
                self.helper.log_info(f"current valhalla status: {api_status}")

                current_time = int(datetime.utcnow().timestamp())
                current_state = self._load_state()

                self.helper.log_info(f"loaded state: {current_state}")

                last_run = self._get_state_value(current_state,
                                                 self._STATE_LAST_RUN)

                last_valhalla_version = self._get_state_value(
                    current_state, self._VALHALLA_LAST_VERSION)

                if self._is_scheduled(
                        last_run, current_time) and self._check_version(
                            last_valhalla_version, api_status.version):
                    self.helper.log_info("running importers")

                    knowledge_importer_state = self._run_knowledge_importer(
                        current_state)
                    self.helper.log_info("done with running importers")

                    new_state = current_state.copy()
                    new_state.update(knowledge_importer_state)
                    new_state[self._STATE_LAST_RUN] = int(
                        datetime.utcnow().timestamp())
                    new_state[self._VALHALLA_LAST_VERSION] = api_status.version

                    self.helper.log_info(f"storing new state: {new_state}")

                    self.helper.set_state(new_state)

                    self.helper.log_info(
                        f"state stored, next run in: {self._get_interval()} seconds"
                    )
                else:
                    new_interval = self._get_interval() - (current_time -
                                                           last_run)
                    self.helper.log_info(
                        f"connector will not run, next run in: {new_interval} seconds"
                    )

                # After a successful run pause at least 60sec
                time.sleep(60)
            except (KeyboardInterrupt, SystemExit):
                self.helper.log_info("connector stop")
                exit(0)
            except Exception as e:
                self.helper.log_error(str(e))
                exit(0)

    def _run_knowledge_importer(
            self, current_state: Mapping[str, Any]) -> Mapping[str, Any]:
        return self.knowledge_importer.run(current_state)

    def _get_interval(self) -> int:
        return int(self.INTERVAL_SEC)

    def _load_state(self) -> Dict[str, Any]:
        current_state = self.helper.get_state()
        if not current_state:
            return {}
        return current_state

    @staticmethod
    def _get_state_value(state: Optional[Mapping[str, Any]],
                         key: str,
                         default: Optional[Any] = None) -> Any:
        if state is not None:
            return state.get(key, default)
        return default

    def _is_scheduled(self, last_run: Optional[int],
                      current_time: int) -> bool:
        if last_run is None:
            return True
        time_diff = current_time - last_run
        return time_diff >= self._get_interval()

    def _check_version(self, last_version: Optional[int],
                       current_version: int) -> bool:
        if last_version is None:
            return True
        return current_version > last_version