Automated notifications for Microsoft account lock-out with Elastic Watcher

In a previous article we saw how we can use Elastic’s Watcher to automatically send notifications when there is no data coming into one of your indexes in Elasticsearch. The watcher from that article was a fairly simple one so today we are looking at a more complex watch.

For the purpose of this article we will take a look at one of the most common and frequent problems that IT support teams have to deal with: account lockouts.

More specifically we will look at Active Directory user account lockouts. Because Active Directory is used in almost every organization and can be integrated with various applications to provide authentication it is often the situation that users either forget their password or confuses it with other accounts. This leads to users trying the wrong password several times and locking their account. In order to be able to login again they require a password reset or a user account unlock from IT support.

We will use Elastic Watcher to automatically send notifications to the IT support team with a list of account names that have been locked out and the domain controller which logged the incident.

Before jumping into the configuration of the watcher a quick reminder that in order to be able to send Slack messages via Watcher you need to configure Watcher appropriately. This guide will not cover the integration part between Watcher and Slack but you can read more about it here (https://www.elastic.co/guide/en/elastic-stack-overview/current/actions-slack.html#configuring-slack).

Watcher has 4 main parts that it is composed of: trigger, input, condition and action. Let’s take a look at each of these components.


{
  "trigger": {
    "schedule": {
      "interval": "30m"
    }
  }

As always we will start by defining the trigger of the watcher by setting a time interval in which the check should be done. The trigger has multiple options that can be used but for this example we will use the “interval” option and set it to 30 minutes which depending on the number of users and logins happening can be adapted to your situation.


 "input": {
    "search": {
      "request": {
        "search_type": "query_then_fetch",
        "indices": [
          "microsoft-user-activity*"
        ],
        "rest_total_hits_as_int": true,
        "body": {
          "query": {
            "bool": {
              "filter": [
                {
                  "range": {
                    "@timestamp": {
                      "from": "now-30m",
                      "to": "now"
                    }
                  }
                }
              ],
              "must": [
                {
                  "match": {
                    "event.code": "4740"
                  }
                }
              ]
            }
          }
        }
      }
    }
  }

The input of the watcher will contain the index in which we will be looking for the events as well as the query which will retrieve the information that we are interested in. For this example we are using a Microsoft index that retrieves data from domain controllers where events such as account lockouts are recorded.

For the query we will use a bool query that consists of a filter on the timestamp field which defines the period of time used for the check and a must match query that specifies that we are interested only in the documents which contain the value 4740 for the field event.code.

If you have ECS applied to your Microsoft index or are using beats version 7.x the field event.code holds the information with regards to the Microsoft event id logged by the domain controller. In this case we use the event ID 4740 which is logged when a user locks out his account.


"condition": {
    "compare": {
      "ctx.payload.hits.total": {
        "gt": 0
      }
    }
  }

The condition of the watcher is used to determine whether the action of the watcher (will be discussed further below) will be triggered. For our example we will set the condition so that if there are any events which have the event.code 4740 the action will be triggered.

As the action for this watcher we are interested in notifying our IT support team over Slack that a user has locked out his account. We also want to provide them with the user name and the host which logged the incident in case they want to do further investigations.

Because it is possible to have more than one user locked out during the 30 minute interval we will use a script to collect the information into a list.


"transform": {
        "script": {
          "source": "['items': ctx.payload.hits.hits.collect(hit -> [ 'User': hit._source.winlog.event_data.TargetUserName, 'Host': hit._source.host.name, 'color': 'danger'])]",
          "lang": "painless"
        }
      }

This script defines a “list” which collects information from the payload and selects some of the information which can be later used in the Slack message to dynamically populate the user name and the host that logged the incident. As such we will define two parameters “User” and “Host” which are mapped to a certain value that can be found in the results of the query. Furthermore we also define the parameter “color” that has the value “danger” which will give the message a dark red line in the Slack channel to indicate the urgency of the message. This parameter can also be set to “warning” for yellow, “good” for green or a hex color code for a custom color.

For the slack message we will use a dynamic_attachment with the parameters defined in the script, so in our case: User and Host. We populate the rest of the fields such as “text” and “title” with appropriate messages and information and choose the channel or user(s) we want to send the message to.


"actions": {
    "notify-slack": {
      "throttle_period_in_millis": 300000,
      "transform": {
        "script": {
          "source": "['items': ctx.payload.hits.hits.collect(hit -> [ 'User': hit._source.winlog.event_data.TargetUserName, 'Host': hit._source.host.name, 'color': 'danger'])]",
          "lang": "painless"
        }
      },
      "slack": {
        "account": "monitoring",
        "message": {
          "from": "IT Support Watcher",
          "to": [
            "#IT-support"
          ],
          "text": "User locked out of account!:",
          "dynamic_attachments": {
            "list_path": "ctx.payload.items",
            "attachment_template": {
              "color": "{{color}}",
              "title": " Below user(s) have been locked out of their accounts:",
              "text": "User: {{User}} on Host: {{Host}}"
            }
          }
        }
      }
    }
  }

This action will send a message to the Slack channel IT-support with a list of all locked out accounts for the past 30 minutes which will include the user’s name and the domain controller that logged the incident. We could also send this message to a specific user by adding the “@” tag and the user ID. It’s important to note that if you use the user name the notifications will not go through. You need to specifically use the user ID from Slack.

Putting it all together


{
  "trigger": {
    "schedule": {
      "interval": "30m"
    }
  },
  "input": {
    "search": {
      "request": {
        "search_type": "query_then_fetch",
        "indices": [
          "microsoft*"
        ],
        "rest_total_hits_as_int": true,
        "body": {
          "query": {
            "bool": {
              "filter": [
                {
                  "range": {
                    "@timestamp": {
                      "from": "now-30m",
                      "to": "now"
                    }
                  }
                }
              ],
              "must": [
                {
                  "match": {
                    "event.code": "4740"
                  }
                }
              ]
            }
          }
        }
      }
    }
  },
  "condition": {
    "compare": {
      "ctx.payload.hits.total": {
        "gt": 0
      }
    }
  },
  "actions": {
    "notify-slack": {
      "throttle_period_in_millis": 300000,
      "transform": {
        "script": {
          "source": "['items': ctx.payload.hits.hits.collect(hit -> [ 'User': hit._source.winlog.event_data.TargetUserName, 'Host': hit._source.host.name, 'color': 'danger'])]",
          "lang": "painless"
        }
      },
      "slack": {
        "account": "monitoring",
        "message": {
          "from": "IT Support Watcher",
          "to": [
            "#IT-support"
          ],
          "text": "User locked out of account!:",
          "dynamic_attachments": {
            "list_path": "ctx.payload.items",
            "attachment_template": {
              "color": "{{color}}",
              "title": " Below user(s) have been locked out of their accounts:",
              "text": "User: {{User}} on Host: {{Host}}"
            }
          }
        }
      }
    }
  }
}

We now have a Watcher configured to automatically send a list of locked out accounts to our IT support team that can be proactive in analyzing the incidents and be in the know when users notify them of the situation.