-
Notifications
You must be signed in to change notification settings - Fork 5
[client] Refactor helpers to expose encapsulated and tested components for building collectors #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
c2a3080
refactor and test OpenBAS configuration helpers
antoinemzs 5775e92
refactored daemons
antoinemzs 5802e02
rewrite collector daemons
antoinemzs 890a954
rewrite expectation typing and signature matching
antoinemzs ef20b2e
test expectations and matching
antoinemzs f05a243
the lintening
antoinemzs e30accc
adjust doc
antoinemzs 0125bb3
restore ci and fix test
antoinemzs 1fcaa62
Add docstrings
antoinemzs 1b5ba1a
expose new packages to users
antoinemzs 489abac
update version which is not yet autoupdated by release script
antoinemzs 753d26c
ConfigurationHint.data should be of union type
antoinemzs c1a867d
black on docstrings + more backwards compat
antoinemzs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .inject_expectation import * # noqa: F401,F403 | ||
RomuDeuxfois marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from .expectation import ( | ||
DetectionExpectation, | ||
ExpectationTypeEnum, | ||
PreventionExpectation, | ||
) | ||
|
||
__all__ = ["DetectionExpectation", "ExpectationTypeEnum", "PreventionExpectation"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
from enum import Enum | ||
from typing import List | ||
from uuid import UUID | ||
|
||
from pydantic import BaseModel | ||
from thefuzz import fuzz | ||
|
||
from pyobas.signatures.signature_type import SignatureType | ||
from pyobas.signatures.types import MatchTypes, SignatureTypes | ||
|
||
|
||
class ExpectationTypeEnum(str, Enum): | ||
"""Types of Expectations""" | ||
|
||
Detection = "DETECTION" | ||
Prevention = "PREVENTION" | ||
Other = "other" | ||
|
||
@classmethod | ||
def _missing_(cls, value): | ||
return cls.Other | ||
|
||
|
||
class ExpectationSignature(BaseModel): | ||
"""An expectation signature describes a known marker potentially | ||
found in alerting data in security software. For example, an | ||
expectation signature can be a process image name, a command | ||
line, or any other relevant piece of data. | ||
""" | ||
|
||
type: SignatureTypes | ||
value: str | ||
|
||
|
||
class Expectation(BaseModel): | ||
"""An expectation represents an expected outcome of a BAS run. | ||
For example, in the case of running an attack command line, the | ||
expectation may be that security software has _detected_ it, while | ||
another expectation may be that the attack was _prevented_. | ||
""" | ||
|
||
inject_expectation_id: UUID | ||
inject_expectation_signatures: List[ExpectationSignature] | ||
|
||
success_label: str = "Success" | ||
failure_label: str = "Failure" | ||
|
||
def __init__(self, *a, **kw): | ||
super().__init__(*a, **kw) | ||
self.__api_client = kw["api_client"] | ||
|
||
def update(self, success, sender_id, metadata): | ||
"""Update the expectation object in OpenBAS with the supplied outcome. | ||
|
||
:param success: whether the expectation was fulfilled (true) or not (false) | ||
:type success: bool | ||
:param sender_id: identifier of the collector that is updating the expectation | ||
:type sender_id: string | ||
:param metadata: arbitrary dictionary of additional data relevant to updating the expectation | ||
:type metadata: dict[string,string] | ||
""" | ||
self.__api_client.update( | ||
self.inject_expectation_id, | ||
inject_expectation={ | ||
"collector_id": sender_id, | ||
"result": (self.success_label if success else self.failure_label), | ||
"is_success": success, | ||
"metadata": metadata, | ||
}, | ||
) | ||
|
||
def match_alert(self, relevant_signature_types: list[SignatureType], alert_data): | ||
"""Matches an alert's data against the current expectation signatures | ||
to see if the alert is relevant to the current expectation's inject, | ||
i.e. this alert was triggered by the execution of the inject to which | ||
belongs the expectation. | ||
|
||
:param relevant_signature_types: filter of signature types that we want to consider. | ||
Only the signature types listed in this collection may be checked for matching. | ||
:type relevant_signature_types: list[SignatureType] | ||
:param alert_data: list of possibly relevant markers found in an alert. | ||
:type alert_data: dict[SignatureTypes, dict] | ||
|
||
:return: whether the alert matches the expectation signatures or not. | ||
:rtype: bool | ||
""" | ||
relevant_expectation_signatures = [ | ||
signature | ||
for signature in self.inject_expectation_signatures | ||
if signature.type in [type.label for type in relevant_signature_types] | ||
] | ||
if not any(relevant_expectation_signatures): | ||
return False | ||
|
||
for relevant_expectation_signature in relevant_expectation_signatures: | ||
if not ( | ||
alert_signature_for_type := alert_data.get( | ||
relevant_expectation_signature.type.value | ||
) | ||
): | ||
return False | ||
|
||
if alert_signature_for_type[ | ||
"type" | ||
] == MatchTypes.MATCH_TYPE_FUZZY and not self.match_fuzzy( | ||
alert_signature_for_type["data"], | ||
relevant_expectation_signature.value, | ||
alert_signature_for_type["score"], | ||
): | ||
return False | ||
if alert_signature_for_type[ | ||
"type" | ||
] == MatchTypes.MATCH_TYPE_SIMPLE and not self.match_simple( | ||
alert_signature_for_type["data"], relevant_expectation_signature.value | ||
): | ||
return False | ||
|
||
return True | ||
|
||
@staticmethod | ||
def match_fuzzy(tested: list[str], reference: str, threshold: int): | ||
"""Applies a fuzzy match against a known reference to a list of candidates | ||
|
||
:param tested: list of strings candidate for fuzzy matching | ||
:type tested: list[str] | ||
:param reference: the reference against which to try to fuzzy match | ||
:type reference: str | ||
:param threshold: string overlap percentage threshold above which to declare a match | ||
:type threshold: int | ||
|
||
:return: whether any of the candidate is a match against the reference | ||
:rtype: bool | ||
""" | ||
actual_tested = [tested] if isinstance(tested, str) else tested | ||
for value in actual_tested: | ||
ratio = fuzz.ratio(value, reference) | ||
if ratio >= threshold: | ||
return True | ||
return False | ||
|
||
@staticmethod | ||
def match_simple(tested: list[str], reference: str): | ||
"""A simple strict, case-sensitive string matching between a list of | ||
candidates and a reference. | ||
|
||
:param tested: list of strings candidate for fuzzy matching | ||
:type tested: list[str] | ||
:param reference: the reference against which to try to fuzzy match | ||
:type reference: str | ||
|
||
:return: whether any of the candidate is a match against the reference | ||
:rtype: bool | ||
""" | ||
return Expectation.match_fuzzy(tested, reference, threshold=100) | ||
|
||
|
||
class DetectionExpectation(Expectation): | ||
"""An expectation that is specific to Detection, i.e. that is used | ||
by OpenBAS to assert that an inject's execution was detected. | ||
""" | ||
|
||
success_label: str = "Detected" | ||
failure_label: str = "Not Detected" | ||
|
||
|
||
class PreventionExpectation(Expectation): | ||
"""An expectation that is specific to Prevention, i.e. that is used | ||
by OpenBAS to assert that an inject's execution was prevented. | ||
""" | ||
|
||
success_label: str = "Prevented" | ||
failure_label: str = "Not Prevented" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .configuration import Configuration | ||
|
||
__all__ = ["Configuration"] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.