在AWS上使用诱饵用户捕获攻击者

思路就是创建一个IAM用户,然后用CloudWatch监控该用户的行为,如果出现了该用户的操作则触发告警,并通过邮件通知。

使用控制台和Terraform两种方法实现

控制台创建

创建诱饵用户

在 IAM 控制台创建一个诱饵用户honeyUser,不附加任何权限
image.png

honeyUser创建一个访问密钥,这对密钥之后便可以通过一些方法泄露出去,进而捕获可疑的攻击者。
image.png

创建CloudTrail并将事件发送至CloudWatch

进入CloudTrail控制台创建一个trail(追踪),创建CloudTrail的时候需要选择一个S3来对日志进行存储,如果没有提前准备好则需要在这里新创建一个。
image.png

这里不使用加密方便后面数据的检索
image.png

配置向CloudWatch Logs发送事件,之后可以在CloudWatch配置规则来对诱饵用户的登录行为进行告警。CloudTrail 使用 CloudWatch 日志组作为日志事件的传输终端节点,需要创建一个日志组或者指定一个现有日志组。同时需要一个 IAM角色 将事件发送到 CloudWatch,这个角色需要有logs:CreateLogStreamlogs:PutLogEvents两个权限。这里让它新建一个日志组和角色。
image.png

在下一步追踪的事件中只选择Management events,主要追踪用户的登录行为
image.png

创建好后在 Trails页面 显示该Trail 已开始工作(Status: Logging)
image.png

在配置CloudWatch之前,我们先看下和这个 Trail 以前创建的都有那些资源。进入 IAM页面策略 中查看客户托管策略,我们可以看到一个新创建的用来推送 CloudWatch 的托管策略。它的描述是:Policy for config CloudWathLogs for trail cloudtrail-logging-honey, created by CloudTrail console
image.png

点击查看它的策略内容。这里许可了两个操作。而他们指向的资源都是 CloudTrailCloudWatch Logs 发送的这条日志流

  • logs:CreateLogStream: 允许被授权的实体创建日志流
  • logs:PutLogEvents: 允许被授权的实体向日志流中写入日志事件。
image.png

进入 角色 可以看到 CloudTrail 创建的角色 CloudTrailRoleForCloudWatch-Honey
image.png

同时它的可信实体指向了指定了 AWS 的 CloudTrail 服务 (cloudtrail.amazonaws.com)
image.png

权限则使用的是刚刚看到的策略组
image.png

CloudWatch 中的日志组中,看到 CloudTrail 创建的日志组 aws-cloudtrail-logs-CloudWatch-Honey
image.png

现在我们在 AWS CLI 中登录之前创建的 honeyUser
image.png

回到 CloudWatch 页面,进入 Live Tail,选择要筛选的日志组
image.png

过了一会儿后看到我们的登录记录
image.png

创建日志筛选并设置告警

选择日志组,操作 -> 创建指标筛选条件
image.png

使用筛选模板筛选出 honeyUser 的所有日志

{ $.userIdentity.type = "IAMUser" && $.userIdentity.userName = "honeyUser" }
image.png

在分配指标页面输入筛选名称、命名空间、指标名称、指标值设置为1
image.png

配置好之后为它创建告警
image.png

周期配置为1分钟,阈值配置为>=1。这意味着在任何给定的 1 分钟周期内,如果该用户至少登录一次,就会触发告警。
image.png
image.png

在下一步配置告警如何处理,这里我们创建一个主题,把告警发送到我们的邮箱。
image.png

在下一步配置下表述
image.png

这样告警就配置好了
image.png

最后在到邮箱里确认下订阅
image.png

在 CLI 中尝试登录 honeyUser,Honey_Alarm 变为告警中
image.png

同时邮箱收到告警
image.png

实验完后记得清理刚刚创建的内容,以免产生额外的费用

  • CloudWatch Log 告警
  • CloudWatch Log 日志组
  • Amazon SNS
  • CloudTrail 追踪
  • S3
  • IAM角色
  • IAM客户托管策略
  • IAM用户 honeyUser

使用Terraform代码创建

Terraform是一个基础设施即代码(IaC)工具,简而言之它让我们可以用代码的方式对云上的各个资源进行创建、配置和更新。

对于上面的创建流程大致可以分为几步:

  • 创建一个IAM用户,作为诱饵用户。
  • 创建一个CloudTrail追踪,并创建一个S3桶来存储日志。
  • 创建一个CloudWatch Log日志组来分析日志,同时还要一个拥有创建/推送日志流权限的IAM角色向日志组中推送日志。
  • 创建一个CloudWatch Log日志筛选,同时为它创建一个CloudWatch Log告警,再创建一个SNS主题用来接收告警,并为它配置好endpoint,也就是我们的邮箱地址。

在使用Terraform代码实现这个流程之前,先准备好一个空目录,里面创建文件结构如下

./
├── main.tf
├── outputs.tf
└── povider.tf

其中:

  • provider.tf:用来配置 AWS povider
  • main.tf:包含了主要的资源配置
  • outputs.tf:用来定义输出变量

本次实验的版本Terraform v1.8.5,同时提前缓存好了AWS povider。下面开始coding

创建AWS用户

首先编写povider.tf,配置好regionAK/SK

provider "aws" {
  region     = "us-east-1"
  access_key = "<aws_access_key>"
  secret_key = "<aws_secret_key>"
}

main.tf创建诱饵用户,同时开启凭证访问

# 诱饵用户 backup_admin
resource "aws_iam_user" "backup_admin" {
  name = "backup-admin"
}

resource "aws_iam_access_key" "backup_admin_key" {
  user = aws_iam_user.backup_admin.name
}

outputs.tf将AK/SK输出。因为密钥属于敏感信息,无法直接在控制台输出,所以要为变量设置sensitive = true。若要查看使用命令:terraform output -json

output "access_key_id" {
  value     = aws_iam_access_key.backup_admin_key.id
  sensitive = true
}

output "secret_access_key" {
  value     = aws_iam_access_key.backup_admin_key.secret
  sensitive = true
}

编辑好之后依次输入命令

terraform init
terraform plan
terraform apply
image.png

这样Terraform就自动为我们创建了这个backup-admin用户
image.png

创建配置CloudTrail

首先定义好当前AWS用户的数据源,之后创建的时候会用到其中的信息

# AWS 元数据
data "aws_caller_identity" "current" {}

data "aws_partition" "current" {}

data "aws_region" "current" {}

然后是创建CloudTrail的代码

# 创建 CloudTrail 
resource "aws_cloudtrail" "cloudtrail_honey" {
  name                          = "cloudtrail_honey"
  s3_bucket_name                = aws_s3_bucket.cloudtrail_bucket.id
  include_global_service_events = true
  is_multi_region_trail         = true

  depends_on = [aws_s3_bucket_policy.cloudtrail_bucket_policy]
}

# CloudTrail 所用的 S3 配置
resource "aws_s3_bucket" "cloudtrail_bucket" {
  bucket        = "cloudtrail-bucket-honey"
  force_destroy = true
}

data "aws_iam_policy_document" "cloudtrail_bucket_policy_document" {
  statement {
    sid    = "AWSCloudTrailAclCheck"
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["cloudtrail.amazonaws.com"]
    }

    actions   = ["s3:GetBucketAcl"]
    resources = [aws_s3_bucket.cloudtrail_bucket.arn]
    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = ["arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/cloudtrail_honey"]
    }
  }

  statement {
    sid    = "AWSCloudTrailWrite"
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["cloudtrail.amazonaws.com"]
    }

    actions   = ["s3:PutObject"]
    resources = ["${aws_s3_bucket.cloudtrail_bucket.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"]

    condition {
      test     = "StringEquals"
      variable = "s3:x-amz-acl"
      values   = ["bucket-owner-full-control"]
    }
    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = ["arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/cloudtrail_honey"]
    }
  }
}

resource "aws_s3_bucket_policy" "cloudtrail_bucket_policy" {
  bucket = aws_s3_bucket.cloudtrail_bucket.id
  policy = data.aws_iam_policy_document.cloudtrail_bucket_policy_document.json
}

下面一步一步说明,首先是 resource "aws_cloudtrail" 块。这里我们定义了一个AWS CloudTrail追踪器其中:

  • name指定的是CloudTrail追踪器的名称为cloudtrail_honey
  • s3_bucket_name指定存储 CloudTrail 日志的 Amazon S3 桶的名称。这里引用了另一个 Terraform 资源 aws_s3_bucket,该资源的 ID 被用作 S3 桶的名称。
  • include_global_service_events = true设置启用了全球服务事件的记录,如 AWS Identity and Access Management (IAM) 操作。这意味着不仅限于地理区域特定的服务,全球服务的事件也会被记录。
  • is_multi_region_trail = true启用多区域追踪,确保所有 AWS 区域中的事件都被记录。
  • depends_on = [aws_s3_bucket_policy.cloudtrail_bucket_policy]这是一个 Terraform 的隐式依赖关系声明,用来确保在创建 CloudTrail 追踪器之前,相关的 S3 桶策略已经被创建和应用。
resource "aws_cloudtrail" "cloudtrail_honey" {
  name                          = "cloudtrail_honey"
  s3_bucket_name                = aws_s3_bucket.cloudtrail_bucket.id
  include_global_service_events = true
  is_multi_region_trail         = true
  
  depends_on = [aws_s3_bucket_policy.cloudtrail_bucket_policy]
}

接下来定义了一个供cloudtrail_honey使用的S3以及该S3的策略。其中

  • force_destroy:当设置为 true 时,这个属性允许 Terraform 在执行 terraform destroy 命令时删除存储桶,即使存储桶中还包含文件。这是为了确保在测试或临时部署的情况下,资源可以被完全清理。
  • data "aws_iam_policy_document":策略文档将要定义 AWS CloudTrail 写入日志文件的权限。
  • policy:将前面定义的 IAM 策略文档(序列化为 JSON 格式)应用到指定的存储桶。这样,存储桶的访问权限就根据这个策略进行了配置。
resource "aws_s3_bucket" "cloudtrail_bucket" {
  bucket        = "cloudtrail-bucket-honey"
  force_destroy = true
}

data "aws_iam_policy_document" "cloudtrail_bucket_policy_document" {
  ......
}

resource "aws_s3_bucket_policy" "cloudtrail_bucket_policy" {
  bucket = aws_s3_bucket.cloudtrail_bucket.id
  policy = data.aws_iam_policy_document.cloudtrail_bucket_policy_document.json
}

S3的策略文档主要是让 CloudTrail 能够将日志存储到该 S3 中。这个策略包含两个主要的声明 (statement):

  • 第一条声明: AWSCloudTrailAclCheck
    • 主体 (Principals):指定服务主体为 cloudtrail.amazonaws.com,意味着这条规则专为 CloudTrail 服务设置。
    • 动作 (Actions):允许动作 s3:GetBucketAcl,使 CloudTrail 能检查存储桶的权限设置。
    • 资源 (Resources):限制权限到特定的 S3 存储桶(aws_s3_bucket.cloudtrail_bucket.arn)。
    • 条件 (Condition):确保只有当请求来自特定 CloudTrail 追踪器 (cloudtrail_honey) 时,才允许访问。通过匹配请求的来源 ARN 来强化安全措施。
  • 第二条声明: AWSCloudTrailWrite
    • 主体 (Principals):同样指定服务主体为 cloudtrail.amazonaws.com。
    • 动作 (Actions):允许动作 s3:PutObject,授权 CloudTrail 向指定路径写入日志文件。
    • 资源 (Resources):指定 CloudTrail 可以写入到的具体对象路径,包括 AWS 账户 ID 和存储桶 ARN 的组合,确保日志数据的组织和隔离。
    • 条件 (Condition)
      • s3:x-amz-acl 确保所有写入的对象设置为 bucket-owner-full-control,这为存储桶所有者提供了对日志文件的完全控制权。
      • aws:SourceArn 条件同第一条声明,保证只有特定 CloudTrail 追踪器的请求才被允许。
data "aws_iam_policy_document" "cloudtrail_bucket_policy_document" {
  statement {
    sid    = "AWSCloudTrailAclCheck"
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["cloudtrail.amazonaws.com"]
    }

    actions   = ["s3:GetBucketAcl"]
    resources = [aws_s3_bucket.cloudtrail_bucket.arn]
    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = ["arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/cloudtrail_honey"]
    }
  }

  statement {
    sid    = "AWSCloudTrailWrite"
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["cloudtrail.amazonaws.com"]
    }

    actions   = ["s3:PutObject"]
    resources = ["${aws_s3_bucket.cloudtrail_bucket.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"]

    condition {
      test     = "StringEquals"
      variable = "s3:x-amz-acl"
      values   = ["bucket-owner-full-control"]
    }
    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = ["arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/cloudtrail_honey"]
    }
  }
}

还是用之前的命令部署,完成之后可以看到生成的 S3 和 CloudTrail追踪器
image.png
image.png

创建CloudWatch Log日志组

创建CloudWatch Log日志组之前,还要要准备一个IAM角色让CloudTrail可以将日志推送到日志组中。

# 修改 cloudtrail,添加写入日志组的角色和日志组
resource "aws_cloudtrail" "cloudtrail_honey" {
  ......
  
  cloud_watch_logs_group_arn    = "${aws_cloudwatch_log_group.cloudwatch_log_group_honey.arn}:*"
  cloud_watch_logs_role_arn     = aws_iam_role.cloudtrail_logging_role.arn
    
  ......
}

# 创建CloudWatch Log日志组
resource "aws_cloudwatch_log_group" "cloudwatch_log_group_honey" {
  name = "cloudwatch-log-group-honey"
}

# 创建 CloudTrail 推送日志组的角色
resource "aws_iam_role" "cloudtrail_logging_role" {
  name               = "cloudtrail-logging-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Principal = {
          Service = "cloudtrail.amazonaws.com"
        }
        Effect = "Allow"
        Sid    = ""
      },
    ]
  })
}

resource "aws_iam_role_policy" "cloudtrail_logging_role_policy" {
  name      = "cloudtrail-logging-role-policy"
  role      = aws_iam_role.cloudtrail_logging_role.id

  policy    = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ],
        Effect   = "Allow",
        Resource = "${aws_cloudwatch_log_group.cloudwatch_log_group_honey.arn}:log-stream:*"
      },
    ]
  })
}

这里和前面控制台不一样的是,IAM角色添加的是一个内联策略而不是一个托管策略。其中对于logs:CreateLogStreamlogs:PutLogEvents权限对应的Resource写法为${aws_cloudwatch_log_group.example.arn}:log-stream:*,这个表示该日志组下的所有日志流。

重新部署,在CloudWatch的Live Tail中查看实时的日志
image.png

创建告警

首先创建一个 CloudWatch Logs 的指标过滤器用来过滤出诱饵账户的行为:

  • Resource: aws_cloudwatch_log_metric_filter
    • log_group_name = aws_cloudwatch_log_group.cloudwatch_log_group_honey.name指定应用该过滤器的日志组。
  • Metric Transformation
    • name = "honeyToken":这是创建的 CloudWatch 指标的名称,当日志匹配到上述模式时,此指标会被触发或更新。
    • namespace = "honeyAlarm":指标的命名空间,命名空间用于隔离各个指标集合,防止命名冲突。
    • value = "1":每当日志匹配到模式时,指标的值增加 1。这意味着你可以通过观察这个指标的变化来检测匹配到的日志事件的频率。
resource "aws_cloudwatch_log_metric_filter" "honeyUser_filter" {
  name           = "honeyUser_metric_filter"
  pattern        = "{ $.userIdentity.type = \"IAMUser\" && $.userIdentity.userName = \"${aws_iam_user.backup_admin.name}\" }"
  log_group_name = aws_cloudwatch_log_group.cloudwatch_log_group_honey.name

  metric_transformation {
    name      = "honeyToken"
    namespace = "honeyAlarm"
    value     = "1"
  }
}

创建告警同时配置接受告警的邮箱:

  • Resource: aws_cloudwatch_metric_alarm
    • comparison_operator = "GreaterThanOrEqualToThreshold":这个比较操作符定义了触发告警的条件,表示当监控的指标值大于或等于设置的阈值时,告警将被触发。
    • evaluation_periods = "1":表示评估期数,即 CloudWatch 会考虑最近的一个指定周期(在这里是60秒)的数据点来判断是否触发告警。
    • metric_name/namespace:告警将监控 honeyAlarm 命名空间下的 metric_name 指标,也就是刚刚配置的内容
    • period = 60:指定的周期(以秒为单位),意味着 CloudWatch 会每60秒收集一次指标数据,并用这些数据来评估是否触发告警。
    • statistic** **= "Sum":统计方法,表示在每个评估周期内,CloudWatch 将对收集的数据点求和来进行比较判断。
    • threshold = 1:告警的触发阈值,结合上述的 comparison_operator,意味着如果在一个周期内(60秒)指标“honeyToken”的总和大于或等于1,则触发告警。
    • alarm_actions = [aws_sns_topic.alarm_topic.arn]:当告警状态变为 ALARM 时,将触发的动作列表。在这里,指定了一个 Amazon SNS 主题的 ARN,当告警触发时,将向这个 SNS 主题发送通知。
# 创建告警
resource "aws_cloudwatch_metric_alarm" "backup_admin_alarm" {
  alarm_name          = "BackupAdminActivityAlarm"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "1"
  metric_name         = "honeyToken"
  namespace           = "honeyAlarm"
  period              = 60
  statistic           = "Sum"
  threshold           = 1
  alarm_description   = "Alarm when backup-admin performs any activity."
  alarm_actions       = [aws_sns_topic.alarm_topic.arn]
}

# 创建SNS并配置邮件
resource "aws_sns_topic" "alarm_topic" {
  name = "backup-admin-activity-topic"
}

resource "aws_sns_topic_subscription" "alarm_subscription" {
  topic_arn = aws_sns_topic.alarm_topic.arn
  protocol  = "email"
  endpoint  = "skky@blu3.com"
}

apply之后去邮箱里面确认订阅便可接受告警信息了。terraform output -json输出账户凭证,然后在CLI登录。
image.png

邮箱收到告警
image.png

实验完之后执行terraform destroy清理掉刚刚创建的所有资源。非常方便😎
image.png

总结

这个回顾这个流程,其中CloudTrail追踪、CloudWatch Log日志组、SNS主题等资源是相对来说不会变的。属于诱饵账户的部分就只有一个IAM用户和它的CloudWatch告警。所以我们的tf代码逻辑上应该分为两个部分:

  • 一部分是基础资源的创建,这里创建完之后要提供一个CloudWatch Log日志组名称和SNS主题。
  • 另一个部分是诱饵资源创建,也就是创建一个IAM用户和并它的告警。

这样之后再创建、修改诱饵资源的时候就可以只专注于诱饵资源的代码了
image.png

参考链接

https://pwnedlabs.io/labs/detect-malicious-activity-with-aws-honey-tokens
https://wiki.teamssix.com/CloudNative/Terraform/terraform-introductory.html
https://lonegunmanb.github.io/introduction-terraform/3.4.%E8%BE%93%E5%87%BA%E5%80%BC.html
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudtrail
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm