Automatic Remediation for Amazon GuardDuty with Dome9 CloudBots

For this blog, we will take a look at how to identify and remediate threats in your cloud environment using Amazon GuardDuty and Dome9 CloudBots.

Identify threats with Amazon GuardDuty

Here you can see the GuardDuty findings below. We will specifically explore a malicious IP talking to an EC2 instance in your environment.

You can specifically filter and drill down into the finding related to the compromised instance.


Remediation with Dome9 CloudBots

When situations arise, the most important thing is to have a pre-defined security playbook or runbook that can be a guiding process for IR and remediation.

First as you launch the stack, you need to input a few variables.

For GD Actions, please navigate here to specifically select which actions you would like cloudbots to take. It should be in the format

    "Backdoor:EC2/XORDDOS": "AUTO: ec2_stop_instance"

Once you create your stack, you should see the below:

The lambda function should have your output SNS and GD_ACTIONS listed below


GuardDuty findings are sent to an SNS topic (CW_CD), to which the Dome9 Cloudbot lambda function subscribes to. Following actions are taken by Dome9 CloudBots

1. Parse SNS event

2. Transform the GuardDuty SNS event into a Dome9 finding format

3. Check GD_ACTIONS and AUTO tag on finding and launch specific CloudBot

5. Remediate with predefined action

This code parses the first record of the GuardDuty finding event stream and looks for the message value. As long as the source of the finding is an “aws.guardduty” finding, it calls GD_transform_event module

### code snippet###
  #Bring the data in and parse the SNS message
def lambda_handler(event, context):
    raw_message = event['Records'][0]['Sns']['Message']
    source_message = json.loads(raw_message)
            source_message = json.loads(raw_message)
        except: # If the event comes through as a dict, take it as it comes
            source_message = raw_message
        # Check for source. Transform it to "Dome9" format if it's not originating from Dome9. 
        # This expects that GD is triggering lambda via SNS. This is needed for running cross-region GD events. 
        if "source" in source_message and source_message["source"] == "aws.guardduty": # GuardDuty event source via CW Events
            text_output_array.append("Event Source: GuardDuty\n")
            gd_transform_module = importlib.import_module('transform_gd_event')
            found_action, text_output, source_message = gd_transform_module.transform_gd_event(source_message)
            if not found_action:

GD_transform_event module

This code parses the SNS event and transforms the message (transform code below) into a Dome9 format to allow appropriate action to take place.

### code snippet### 
 def transform_gd_event(unformatted_message):
    found_action = False
    formatted_message = ""
    text_output = ""
    #Check the OS variables to get the list of what we want to do for the different GD actions
        gd_actions = json.loads(os.environ['GD_ACTIONS'])

        for gd_finding_type, action in gd_actions.items():
            if unformatted_message["detail"]["type"] == gd_finding_type and "AUTO:" in action:
                text_output = "Found a defined rule for GD finding %s. Continuing\n" % gd_finding_type
                found_action = True

 # code snippet
        # Make the main structure of the formatted message
        formatted_message = {
            "reportTime": unformatted_message["detail"]["createdAt"],
            "rule": {
                "name": action,
                "complianceTags": action
            "status": "Failed",
            "account": {
                "id": unformatted_message["detail"]["accountId"],
                "vendor": "AWS"
            "entity": {
                "region": unformatted_message["detail"]["region"]

The function takes the JSON message, breaks into chunks and checks for a  specific tag: UnauthorizedAccess:EC2/MaliciousIPCaller.Custom and looks for the specific tag under GD_ACTIONS. In this case, I wanted to stop the instance so I used: {“UnauthorizedAccess:EC2/MaliciousIPCaller.Custom”: “AUTO: ec2_stop_instance”} The function then calls run_action to stop the affected EC2 instance

def handle_event(message,text_output_array):
    post_to_sns = True
    #Break out the values from the JSON payload from Dome9
    rule_name = message['rule']['name']
    status = message['status']
    entity_id = message['entity']['id']
    entity_name = message['entity']['name']
    region = message['entity']['region']
    compliance_tags = message['rule']['complianceTags'].split("|")
## removed code ##
  for tag in compliance_tags:
        tag = tag.strip() #Sometimes the tags come through with trailing or leading spaces. 
        #Check the tag to see if we have AUTO: in it
        pattern = re.compile("^AUTO:\s.+")
        if pattern.match(tag):
            text_output_array.append("Rule violation found: %s \nID: %s | Name: %s \nRemediation bot: %s \n" % (rule_name, entity_id, entity_name, tag))
            # Pull out only the bot verb to run as a function
            # The format is AUTO: bot_name param1 param2
            arr = tag.split(' ')
## removed code
## Run the bot ##
   bot_msg = bot_module.run_action(boto_session,message['rule'],message['entity'],params)

The bot code

  ### Turn off EC2 instance ###
def run_action(boto_session,rule,entity,params):
instance = entity['id']
ec2_client = boto_session.client('ec2')

result = ec2_client.stop_instances(InstanceIds=[instance])
responseCode = result['ResponseMetadata']['HTTPStatusCode'] if responseCode >= 400: text_output = "Unexpected error: %s \n" % str(result) else: text_output = "Instance stopped: %s \n" % instance return text_output

You should see EC2 instance stopped:


Explore logs