在针对 AWS 的网络攻击过程中,有一类攻击是暴力破解 root 账号,此类攻击会被 GuardDuty 监控并记录到。当发生此类暴力破解事件的时候,可以通过 Lambda 来对攻击源进行及时封禁。

Lambda 函数

下面的 Lambda 函数是基于 GuardDuty 对一个 RDP 暴力破解事件进行的 响应,使用 nodejs 编写。

const AWS = require('aws-sdk');
const innerCidrs = ["172.31.0.0/16", "172.33.0.0/16"]; //内网 IP cidr。
const protectedVpcIds = ['vpc-xxxxxxxx']; //当外部攻击时候,需要保护的 VPC。
const forbiddenSgId = "sg-xxxxxxxxxxxx"; //预定义的隔离安全组 ID,此安全组规则是全部封禁。
const region = 'cn-northwest-1';
AWS.config.update({region});
const ec2 = new AWS.EC2();
exports.handler = async (event, context) => {
  const sourceIp = event.detail.network.sourceIpV4;
  const attackType = event.detail.type;
  const isInnerIp = checkInnerIp(sourceIp);
  if (isInnerIp && attackType.indexOf("RDPBruteForce") > 0){
    console.log("这是内部 IP,直接修改目标主机安全组进行隔离。");
    try {
      const ec2 = await findEC2(sourceIp);
      if (ec2) {
        await banInnerEc2(ec2.InstanceId);
      }
    }catch(ex){
      console.error(ex);
    }
  } else {
    console.log("这是外部 IP,建议调用 网络防火墙 API(如 fortnet)进行网络阻断。");
    
    // await banExternalIp(sourceIp);
  }
  const response = {
      statusCode: 200,
      body: "OK"
  };
  return response;
};

// 判断是否是内部 IP
function checkInnerIp(ipAddress){
  const cidrLen = innerCidrs.length;
  for (let i=0;i<cidrLen;i++){
    if (checkIpInCidr(innerCidrs[i], ipAddress)) {
      return true;
    }
  }
  return false;
}

function checkIpInCidr(cidr, ipAddress){
  const ipInt = ipToInt(ipAddress);
  const res = parseCIDR(cidr);
  if (ipInt >= res.start && ipInt<=res.end){
    return true;
  }
  return false;
}

function ipToInt(ip){
  const subnet = ip.split('.').map(Number);
  return (subnet[0] << 24) + (subnet[1] << 16) + (subnet[2] << 8) + subnet[3];
}

function parseCIDR(cidr) {
  const [subnetAddress, mask] = cidr.split('/');
  const maskBits = Number(mask);
  const subnetInt = ipToInt(subnetAddress);
  const subnetMaskInt = ((1 << maskBits) - 1) << (32 - maskBits);
  const start = subnetInt & subnetMaskInt;
  const end = start + (1 << (32 - maskBits)) - 1;
  return { start, end };
}

//通过 ip 寻找 EC2 实例
function findEC2(privateIpAddress){
  const ec2 = new AWS.EC2({ region, apiVersion: '2016-11-15' });
  const params = {
    Filters: [
      {
        Name: 'private-ip-address',
        Values: [privateIpAddress],
      },
    ],
  };
  return new Promise((resolve, reject) => {
    ec2.describeInstances(params, (err, data) => {
      if (err) { reject(err); }
      else { 
        const instances = data.Reservations.flatMap(reservation => reservation.Instances);
         if (instances.length === 0) {
           resolve(null);
          } else {
            const instance = instances[0];
            resolve(instance);
          }
        resolve(data); 
      }
    });
  });
}

// 封禁 VPC 内 EC2
function banInnerEc2(instanceId){
  const params = {
    InstanceId: instanceId,
    Groups: [forbiddenSgId]
  };
  return ec2.modifyInstanceAttribute(params).promise();
}


async function banExternalIp(ip){
  for (let vpcId of protectedVpcIds){
    await banExternalIpInVpc(vpcId, ip);
  }
}

// TODO: 绑定子网。
// 需要注意:多 NACL 策略需要确认其优先顺序,通常修改 NACL 会有网络风险,实际环境中建议通过外部防火墙进行封禁。
async function banExternalIpInVpc(vpcId, ip){
  const aclParams = {
    VpcId: vpcId
  };
  
  const result = await ec2.createNetworkAcl(aclParams).promise();
  const aclId = result.NetworkAcl.NetworkAclId;
  
  const allowAllInRuleParams = {
    NetworkAclId: aclId,
    RuleNumber: 200,
    Protocol: '-1',
    RuleAction: 'allow',
    Egress: false,
    CidrBlock: `0.0.0.0/0`
  };
  const allowAllOutRuleParams = {
    NetworkAclId: aclId,
    RuleNumber: 200,
    Protocol: '-1',
    RuleAction: 'allow',
    Egress: true,
    CidrBlock: `0.0.0.0/0`
  };
  
  const denyRuleParams = {
    NetworkAclId: aclId,
    RuleNumber: 1,
    Protocol: '-1',
    RuleAction: 'deny',
    Egress: false,
    CidrBlock: `${ip}/32`
  };
  
  await ec2.createNetworkAclEntry(allowAllInRuleParams).promise();
  await ec2.createNetworkAclEntry(allowAllOutRuleParams).promise();
  await ec2.createNetworkAclEntry(denyRuleParams).promise();
  
}

程序思路:

  • 从攻击事件中得到攻击源的 IP,判断 IP 是否是内网IP。
  • 如果是 内网 IP,那么则找到当前 IP 对应的主机,并把此主机加入一个特殊的安全组。
  • 如果是外网 IP,则调用网络防火墙进行封禁(或者通过设置 NACL 进行封禁)。
  • 此例子仅适用 IPv4。

假设以上函数命名为 LockTarget

现在模拟一个 GuardDuty 的攻击事件,我测试的 GuardDuty 的事件格式如下:

{
  "version": "2.0",
  "id": "12345678-1234-1234-1234-123456789012",
  "detail-type": "GuardDuty Finding",
  "source": "aws.guardduty",
  "account": "123456789012",
  "time": "2022-02-25T17:01:23Z",
  "region": "",
  "resources": [
    {
      "type": "AwsEc2Instance",
      "id": "i-01234567890abcdef"
    }
  ],
  "detail": {
    "schemaVersion": "3.3",
    "accountId": "123456789012",
    "partition": "aws",
    "region": "cn-northwest-1",
    "id": "EXAMPLE-GUARDDUTY-FINDING-ID",
    "arn": "arn:aws:guardduty:us-west-2:123456789012:detector/EXAMPLE_DETECTOR_ID/finding/EXAMPLE-GUARDDUTY-FINDING-ID",
    "type": "UnauthorizedAccess:EC2/RDPBruteForce",
    "createdAt": "2022-02-25T16:59:33.185Z",
    "updatedAt": "2022-02-25T16:59:33.185Z",
    "severity": 7,
    "confidence": 90,
    "title": "EC2 Instance 123.45.67.89 is involved in RDP brute force attack",
    "description": "....",
    "resource": {
      "resourceType": "Instance",
      "instanceDetails": {
        "instanceId": "i-01234567890abcdef",
        "instanceType": "t2.micro",
        "launchTime": "2022-02-25T16:46:47Z",
        "platform": "windows",
        "networkInterfaces": [
          {
            "ipv4Addresses": [
              "123.45.67.89"
            ],
            "networkInterfaceId": "eni-01234567890abcdef",
            "subnetId": "subnet-01234567890abcdef",
            "vpcId": "vpc-01234567890abcdef"
          }
        ]
      }
    },
    "service": {
      "serviceName": "guardduty",
      "detectorId": "EXAMPLE_DETECTOR_ID",
      "eventFirstSeen": "2022-02-25T16:46:47Z",
      "eventLastSeen": "2022-02-25T16:46:47Z",
      "archived": false
    },
    "additionalInfo": {
      "ThreatListName": "Example Threat List",
      "DetectTime": "2022-02-25T16:46:47Z",
      "DetectContentType": "application/octet-stream",
      "AttackType": "RDP brute force"
    },
    "network": {
      "direction": "IN",
      "protocol": "RDP",
      "sourceIpV4": "192.31.8.115"
    }
  }
}
  • 上述代码出自于 ChatGPT,和实际的 GuardDuty 事件可能会稍有出入,请使用真实的 JSON 结构获取攻击源 IP。

配置

现在在 EventBridge 中创建一个规则:

  • 选择具有事件模式的规则
  • 事件模式选择 GuardDuty, GuardDuty Findings
  • 目标选择:Lambda 函数, LockTarget

通过以上的代码和设置,我们即可将攻击源直接隔离。