From f367df8baa9deba3c361620f87c98eb88c2e234c Mon Sep 17 00:00:00 2001 From: Jonathan Claudius Date: Tue, 28 May 2019 15:17:05 -0400 Subject: [PATCH 01/58] Add prototype LDAP password spray alert --- alerts/ldap_password_spray.py | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 alerts/ldap_password_spray.py diff --git a/alerts/ldap_password_spray.py b/alerts/ldap_password_spray.py new file mode 100644 index 00000000..6e061908 --- /dev/null +++ b/alerts/ldap_password_spray.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# Copyright (c) 2014 Mozilla Corporation + + +from lib.alerttask import AlertTask +from mozdef_util.query_models import SearchQuery, TermMatch + + +class AlertLdapPasswordSpray(AlertTask): + def main(self): + # TODO: evaluate what the threshold should be to detect a password spray + search_query = SearchQuery(minutes=60) + + search_query.add_must([ + TermMatch('category', 'ldap'), + TermMatch('tags', 'ldap'), + TermMatch('details.response.error', 'LDAP_INVALID_CREDENTIALS') + ]) + + self.filtersManual(search_query) + self.searchEventsAggregated('details.client', samplesLimit=10) + + # TODO: evaluate what the threshold should be for this alert + self.walkAggregations(threshold=1) + + # Set alert properties + def onAggregation(self, aggreg): + category = 'ldap' + tags = ['ldap'] + severity = 'WARNING' + + user_dn_list = set() + + for event in aggreg['allevents']: + for request in event['_source']['details']['requests']: + user_dn_list.add(request['details'][0]) + + summary = 'Possible Password Spray Attack in Progress from {0} using the following distinguished names: {1}'.format( + aggreg['value'], + ",".join(sorted(user_dn_list)) + ) + + return self.createAlertDict(summary, category, tags, aggreg['events'], severity) From ce4b7b938862c36789db511670af85acd55bbb7e Mon Sep 17 00:00:00 2001 From: Jonathan Claudius Date: Thu, 6 Jun 2019 08:08:54 -0400 Subject: [PATCH 02/58] Move config items to config file --- alerts/ldap_password_spray.conf | 3 +++ alerts/ldap_password_spray.py | 41 ++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 alerts/ldap_password_spray.conf diff --git a/alerts/ldap_password_spray.conf b/alerts/ldap_password_spray.conf new file mode 100644 index 00000000..aca4ae85 --- /dev/null +++ b/alerts/ldap_password_spray.conf @@ -0,0 +1,3 @@ +[options] +threshold_count = 1 +search_depth_min = 60 \ No newline at end of file diff --git a/alerts/ldap_password_spray.py b/alerts/ldap_password_spray.py index 6e061908..ce92409a 100644 --- a/alerts/ldap_password_spray.py +++ b/alerts/ldap_password_spray.py @@ -8,24 +8,21 @@ from lib.alerttask import AlertTask from mozdef_util.query_models import SearchQuery, TermMatch +import re class AlertLdapPasswordSpray(AlertTask): def main(self): - # TODO: evaluate what the threshold should be to detect a password spray - search_query = SearchQuery(minutes=60) - + self.parse_config('ldap_password_spray.conf', ['threshold_count', 'search_depth_min']) + search_query = SearchQuery(minutes=int(self.config.search_depth_min)) search_query.add_must([ TermMatch('category', 'ldap'), TermMatch('tags', 'ldap'), TermMatch('details.response.error', 'LDAP_INVALID_CREDENTIALS') ]) - self.filtersManual(search_query) self.searchEventsAggregated('details.client', samplesLimit=10) - - # TODO: evaluate what the threshold should be for this alert - self.walkAggregations(threshold=1) + self.walkAggregations(threshold=int(self.config.threshold_count)) # Set alert properties def onAggregation(self, aggreg): @@ -33,15 +30,37 @@ class AlertLdapPasswordSpray(AlertTask): tags = ['ldap'] severity = 'WARNING' - user_dn_list = set() + email_list = set() for event in aggreg['allevents']: for request in event['_source']['details']['requests']: - user_dn_list.add(request['details'][0]) + email = extractEmail(request['details'][0]) + if email: + email_list.add(extractEmail(request['details'][0])) - summary = 'Possible Password Spray Attack in Progress from {0} using the following distinguished names: {1}'.format( + # If no emails, don't throw alert + if len(email_list) == 0: + return None + + summary = 'Password Spray Attack in Progress from {0} targeting the following accounts: {1}'.format( aggreg['value'], - ",".join(sorted(user_dn_list)) + ",".join(sorted(email_list)) ) return self.createAlertDict(summary, category, tags, aggreg['events'], severity) + + # A helper function to extract the first email in a string. + # + # Example: + # + # Input: 'dn="mail=user@example.com,o=com,dc=example"' + # Ouput: user@example.com + # + def extractEmail(string): + email_regex = r'mail=([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)' + match_object = re.match(email_regex, string) + + if match_object: + return match_object.group(1) + else: + return None From 3e206649089a9a5f46e3fef86675250a67ce6661 Mon Sep 17 00:00:00 2001 From: Jonathan Claudius Date: Thu, 6 Jun 2019 08:30:45 -0400 Subject: [PATCH 03/58] Add unit-test stub for LDAP password spray alert --- alerts/ldap_password_spray.py | 3 +- tests/alerts/test_ldap_password_spray.py | 98 ++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 tests/alerts/test_ldap_password_spray.py diff --git a/alerts/ldap_password_spray.py b/alerts/ldap_password_spray.py index ce92409a..017b280b 100644 --- a/alerts/ldap_password_spray.py +++ b/alerts/ldap_password_spray.py @@ -17,7 +17,6 @@ class AlertLdapPasswordSpray(AlertTask): search_query = SearchQuery(minutes=int(self.config.search_depth_min)) search_query.add_must([ TermMatch('category', 'ldap'), - TermMatch('tags', 'ldap'), TermMatch('details.response.error', 'LDAP_INVALID_CREDENTIALS') ]) self.filtersManual(search_query) @@ -42,7 +41,7 @@ class AlertLdapPasswordSpray(AlertTask): if len(email_list) == 0: return None - summary = 'Password Spray Attack in Progress from {0} targeting the following accounts: {1}'.format( + summary = 'Password Spray Attack in Progress from {0} targeting the following account(s): {1}'.format( aggreg['value'], ",".join(sorted(email_list)) ) diff --git a/tests/alerts/test_ldap_password_spray.py b/tests/alerts/test_ldap_password_spray.py new file mode 100644 index 00000000..bc695082 --- /dev/null +++ b/tests/alerts/test_ldap_password_spray.py @@ -0,0 +1,98 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# Copyright (c) 2017 Mozilla Corporation +from positive_alert_test_case import PositiveAlertTestCase +from negative_alert_test_case import NegativeAlertTestCase +from alert_test_suite import AlertTestSuite + + +class TestAlertLdapPasswordSpray(AlertTestSuite): + alert_filename = "ldap_password_spray" + # This event is the default positive event that will cause the + # alert to trigger + default_event = { + "_source": { + "category": "ldap", + "details": { + "client": "1.2.3.4", + "requests": [ + { + 'verb': 'BIND', + 'details': [ + 'dn="mail=jsmith@example.com,o=com,dc=example"', + 'method=128' + ] + } + ], + "response": { + "error": 'LDAP_INVALID_CREDENTIALS', + } + } + } + } + + # This alert is the expected result from running this task + default_alert = { + "category": "ldap", + "tags": ["ldap"], + "severity": "WARNING", + "summary": "Password Spray Attack in Progress from 1.2.3.4 targeting the following account(s): jsmith@example.com", + } + + # This alert is the expected result from this task against multiple matching events + default_alert_aggregated = AlertTestSuite.copy(default_alert) + default_alert_aggregated[ + "summary" + ] = "Password Spray Attack in Progress from 1.2.3.4 targeting the following account(s): jsmith@example.com" + + test_cases = [] + + test_cases.append( + PositiveAlertTestCase( + description="Positive test with default events and default alert expected", + events=AlertTestSuite.create_events(default_event, 1), + expected_alert=default_alert, + ) + ) + + test_cases.append( + PositiveAlertTestCase( + description="Positive test with default events and default alert expected - dedup", + events=AlertTestSuite.create_events(default_event, 2), + expected_alert=default_alert, + ) + ) + + events = AlertTestSuite.create_events(default_event, 10) + for event in events: + event["_source"]["details"]["response"]["error"] = "LDAP_SUCCESS" + test_cases.append( + NegativeAlertTestCase( + description="Negative test with default negative event", events=events + ) + ) + + events = AlertTestSuite.create_events(default_event, 10) + for event in events: + event["_source"]["category"] = "bad" + test_cases.append( + NegativeAlertTestCase( + description="Negative test case with events with incorrect category", + events=events, + ) + ) + + events = AlertTestSuite.create_events(default_event, 10) + for event in events: + event["_source"][ + "utctimestamp" + ] = AlertTestSuite.subtract_from_timestamp_lambda({"minutes": 241}) + event["_source"][ + "receivedtimestamp" + ] = AlertTestSuite.subtract_from_timestamp_lambda({"minutes": 241}) + test_cases.append( + NegativeAlertTestCase( + description="Negative test case with old timestamp", events=events + ) + ) From 5022e63c1e887dbb2e0b0f8c41b9151d4dc681b4 Mon Sep 17 00:00:00 2001 From: Jonathan Claudius Date: Thu, 6 Jun 2019 08:41:55 -0400 Subject: [PATCH 04/58] Minor tweaks to eliminate errors in unit-test runs --- alerts/ldap_password_spray.py | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/alerts/ldap_password_spray.py b/alerts/ldap_password_spray.py index 017b280b..ff835ab4 100644 --- a/alerts/ldap_password_spray.py +++ b/alerts/ldap_password_spray.py @@ -23,23 +23,22 @@ class AlertLdapPasswordSpray(AlertTask): self.searchEventsAggregated('details.client', samplesLimit=10) self.walkAggregations(threshold=int(self.config.threshold_count)) - # Set alert properties def onAggregation(self, aggreg): category = 'ldap' tags = ['ldap'] severity = 'WARNING' - email_list = set() + email_regex = r'mail=([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)' for event in aggreg['allevents']: for request in event['_source']['details']['requests']: - email = extractEmail(request['details'][0]) - if email: - email_list.add(extractEmail(request['details'][0])) + match_object = re.match(email_regex, request['details'][0]) + if match_object: + email_list.add(match_object.group(1)) # If no emails, don't throw alert - if len(email_list) == 0: - return None + # if len(email_list) == 0: + # return None summary = 'Password Spray Attack in Progress from {0} targeting the following account(s): {1}'.format( aggreg['value'], @@ -47,19 +46,3 @@ class AlertLdapPasswordSpray(AlertTask): ) return self.createAlertDict(summary, category, tags, aggreg['events'], severity) - - # A helper function to extract the first email in a string. - # - # Example: - # - # Input: 'dn="mail=user@example.com,o=com,dc=example"' - # Ouput: user@example.com - # - def extractEmail(string): - email_regex = r'mail=([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)' - match_object = re.match(email_regex, string) - - if match_object: - return match_object.group(1) - else: - return None From c89f5ea39847f6a29588162a4bf4a4e3be1d60b2 Mon Sep 17 00:00:00 2001 From: Jonathan Claudius Date: Thu, 6 Jun 2019 08:55:15 -0400 Subject: [PATCH 05/58] Get unit-tests passing --- alerts/ldap_password_spray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alerts/ldap_password_spray.py b/alerts/ldap_password_spray.py index ff835ab4..1a1f22af 100644 --- a/alerts/ldap_password_spray.py +++ b/alerts/ldap_password_spray.py @@ -28,7 +28,7 @@ class AlertLdapPasswordSpray(AlertTask): tags = ['ldap'] severity = 'WARNING' email_list = set() - email_regex = r'mail=([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)' + email_regex = r'.*mail=([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)' for event in aggreg['allevents']: for request in event['_source']['details']['requests']: From cf6ab524911254db55396fdb1310d1abdf96e477 Mon Sep 17 00:00:00 2001 From: Brandon Myers Date: Mon, 8 Jul 2019 14:05:00 -0500 Subject: [PATCH 06/58] Fixup imports from python3 upgrade --- tests/alerts/test_ldap_password_spray.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/alerts/test_ldap_password_spray.py b/tests/alerts/test_ldap_password_spray.py index bc695082..8f780cbc 100644 --- a/tests/alerts/test_ldap_password_spray.py +++ b/tests/alerts/test_ldap_password_spray.py @@ -2,9 +2,10 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # Copyright (c) 2017 Mozilla Corporation -from positive_alert_test_case import PositiveAlertTestCase -from negative_alert_test_case import NegativeAlertTestCase -from alert_test_suite import AlertTestSuite +from .positive_alert_test_case import PositiveAlertTestCase +from .negative_alert_test_case import NegativeAlertTestCase + +from .alert_test_suite import AlertTestSuite class TestAlertLdapPasswordSpray(AlertTestSuite): From 2b0ba950b3035c9224c714869f081d8b88b21b58 Mon Sep 17 00:00:00 2001 From: Gene Wood Date: Wed, 10 Jul 2019 13:20:30 -0700 Subject: [PATCH 07/58] Clarify summary placeholder text and remove trailing quote Fixes #1307 --- meteor/client/incidentAdd.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meteor/client/incidentAdd.html b/meteor/client/incidentAdd.html index d47bd1ea..b5cb9862 100644 --- a/meteor/client/incidentAdd.html +++ b/meteor/client/incidentAdd.html @@ -13,7 +13,7 @@ Copyright (c) 2014 Mozilla Corporation
-
@@ -50,4 +50,4 @@ Copyright (c) 2014 Mozilla Corporation - \ No newline at end of file + From ef922aee902fa20a71e2dd11bd41e96d128d3a5c Mon Sep 17 00:00:00 2001 From: Phrozyn Date: Fri, 12 Jul 2019 13:48:39 -0500 Subject: [PATCH 08/58] Updating veris html to remove data-target and css to ensure text visibility --- meteor/client/verisTags.html | 2 +- meteor/imports/themes/classic/mozdef.css | 75 ++++++++++++++----- meteor/imports/themes/dark/mozdef.css | 63 +++++++++++++--- .../imports/themes/side_nav_dark/mozdef.css | 73 +++++++++++++----- 4 files changed, 165 insertions(+), 48 deletions(-) diff --git a/meteor/client/verisTags.html b/meteor/client/verisTags.html index 9438ae4a..955f8793 100644 --- a/meteor/client/verisTags.html +++ b/meteor/client/verisTags.html @@ -13,7 +13,7 @@ Copyright (c) 2014 Mozilla Corporation placeholder="tag filter">