From 981b0df128084b49bd0f7f4c9b131b91b5777816 Mon Sep 17 00:00:00 2001 From: alphadev4 Date: Fri, 8 Nov 2024 19:48:36 +0500 Subject: [PATCH] bundles_sync/08_11_24 --- helpers/aws/functions.js | 253 +++++++++++++----- helpers/azure/functions.js | 33 ++- helpers/google/functions.js | 151 ++++++++++- plugins/aws/ec2/ec2NetworkExposure.js | 12 +- plugins/aws/guardduty/eksProtectionEnabled.js | 14 +- .../virtualmachines/vmNetworkExposure.js | 40 ++- .../google/compute/instanceNetworkExposure.js | 42 ++- 7 files changed, 450 insertions(+), 95 deletions(-) diff --git a/helpers/aws/functions.js b/helpers/aws/functions.js index 3c18d5deb7..4791e488da 100644 --- a/helpers/aws/functions.js +++ b/helpers/aws/functions.js @@ -1148,8 +1148,7 @@ var checkTags = function(cache, resourceName, resourceList, region, results, set }); }; - -function checkSecurityGroup(securityGroup, cache, region) { +function checkSecurityGroup(securityGroup, cache, region, checkENIs = true) { let allowsAllTraffic; for (var p in securityGroup.IpPermissions) { var permission = securityGroup.IpPermissions[p]; @@ -1171,15 +1170,84 @@ function checkSecurityGroup(securityGroup, cache, region) { } } - if (allowsAllTraffic) { + if (allowsAllTraffic && checkENIs) { return checkNetworkInterface(securityGroup.GroupId, securityGroup.GroupName, '', region, null, securityGroup, cache, true); } - return false; + return allowsAllTraffic; } -var checkNetworkExposure = function(cache, source, subnetId, securityGroups, region, results) { +var getAttachedELBs = function(cache, source, region, resourceId, lbField, lbAttribute) { + let elbs = []; + + // check classice ELBs + var describeLoadBalancers = helpers.addSource(cache, source, + ['elb', 'describeLoadBalancers', region]); + + if (describeLoadBalancers && !describeLoadBalancers.err && describeLoadBalancers.data && describeLoadBalancers.data.length) { + elbs = describeLoadBalancers.data.filter(lb => lb[lbField] && lb[lbField].some(instance => instance[lbAttribute] === resourceId)); + } + + // check ALBs/NLBs + + var describeLoadBalancersv2 = helpers.addSource(cache, source, + ['elbv2', 'describeLoadBalancers', region]); + + if (describeLoadBalancersv2 && !describeLoadBalancersv2.err && describeLoadBalancersv2.data && describeLoadBalancersv2.data.length) { + describeLoadBalancersv2.data.forEach(function(lb) { + lb.targetGroups = []; + var describeTargetGroups = helpers.addSource(cache, source, + ['elbv2', 'describeTargetGroups', region, lb.DNSName]); + + if (describeTargetGroups && !describeTargetGroups.err && describeTargetGroups.data && describeTargetGroups.data.TargetGroups && describeTargetGroups.data.TargetGroups.length) { + describeTargetGroups.data.TargetGroups.forEach(function(tg) { + var describeTargetHealth = helpers.addSource(cache, source, + ['elbv2', 'describeTargetHealth', region, tg.TargetGroupArn]); + + if (describeTargetHealth && !describeTargetHealth.err && describeTargetHealth.data + && describeTargetHealth.data.TargetHealthDescriptions && describeTargetHealth.data.TargetHealthDescriptions.length) { + describeTargetHealth.data.TargetHealthDescriptions.forEach(healthDescription => { + if (healthDescription.Target && healthDescription.Target.Id && + healthDescription.Target.Id === resourceId) { + lb.targetGroups.push({targetgroupName: tg.TargetGroupName, targetGroupArn: tg.TargetGroupArn}); + } + }); + } + }); + } + + if (lb.targetGroups && lb.targetGroups.length) { + let hasListener = false; + var describeListeners = helpers.addSource(cache, source, + ['elbv2', 'describeListeners', region, lb.DNSName]); + if (describeListeners && describeListeners.data && describeListeners.data.Listeners && describeListeners.data.Listeners.length) { + describeListeners.data.Listeners.forEach(listener => { + if (!hasListener) { + hasListener = listener.DefaultActions.some(action => + action.TargetGroupArn && lb.targetGroups.some(tg => tg.targetGroupArn === action.TargetGroupArn) + ); + } + + }); + } + if (hasListener) { + elbs.push(lb); + } + } + }); + } + + return elbs; +}; + +var checkNetworkExposure = function(cache, source, subnets, securityGroups, elbs, region, results, resource) { var internetExposed = ''; + var isSubnetPrivate = false; + + // Check public endpoint access for specific resources like EKS + if (resource && resource.resourcesVpcConfig && resource.resourcesVpcConfig.endpointPublicAccess) { + return 'public endpoint access'; + } // Scenario 1: check if resource is in a private subnet let subnetRouteTableMap, privateSubnets; var describeSubnets = helpers.addSource(cache, source, @@ -1193,80 +1261,139 @@ var checkNetworkExposure = function(cache, source, subnetId, securityGroups, reg } else if (!describeSubnets || describeSubnets.err || !describeSubnets.data) { helpers.addResult(results, 3, 'Unable to query for subnets: ' + helpers.addError(describeSubnets), region); - } else if (describeSubnets.data.length && subnetId) { + } else if (describeSubnets.data.length && subnets.length) { subnetRouteTableMap = getSubnetRTMap(describeSubnets.data, describeRouteTables.data); privateSubnets = getPrivateSubnets(subnetRouteTableMap, describeSubnets.data, describeRouteTables.data); - if (privateSubnets && privateSubnets.length && privateSubnets.find(subnet => subnet === subnetId)) { + if (privateSubnets && privateSubnets.length) { + isSubnetPrivate = !subnets.some(subnet => !privateSubnets.includes(subnet.id)); + } + + // if it's in a private subnet and has no ELBs attached then its not exposed + if (isSubnetPrivate && (!elbs || !elbs.length)) { return ''; } - // If the subnet is not private we will check if security groups and Network ACLs allow internal traffic } + + // If the subnet is not private we will check if security groups and Network ACLs allow internal traffic + // Scenario 2: check if security group allows all traffic var describeSecurityGroups = helpers.addSource(cache, source, ['ec2', 'describeSecurityGroups', region]); + if (!isSubnetPrivate) { + if (!describeSecurityGroups || describeSecurityGroups.err || !describeSecurityGroups.data) { + helpers.addResult(results, 3, + 'Unable to query for security groups: ' + helpers.addError(describeSecurityGroups), region); + } else if (describeSecurityGroups.data.length && securityGroups && securityGroups.length) { + let instanceSGs = describeSecurityGroups.data.filter(sg => securityGroups.find(isg => isg.GroupId === sg.GroupId)); + for (var group of instanceSGs) { + let exposedSG = checkSecurityGroup(group, cache, region); + if (exposedSG) { + internetExposed += internetExposed ? `, ${exposedSG}` : exposedSG; + } + } + } + - if (!describeSecurityGroups || describeSecurityGroups.err || !describeSecurityGroups.data) { - helpers.addResult(results, 3, - 'Unable to query for security groups: ' + helpers.addError(describeSecurityGroups), region); - } else if (describeSecurityGroups.data.length && securityGroups && securityGroups.length) { - let instanceSGs = describeSecurityGroups.data.filter(sg => securityGroups.find(isg => isg.GroupId === sg.GroupId)); - for (var group of instanceSGs) { - let exposedSG = checkSecurityGroup(group, cache, region); - if (!exposedSG) { - return ''; - } else { - internetExposed += exposedSG; + // if security group allows all traffic we need to check NACLs + if (internetExposed.length) { + let subnetIds = subnets.map(s => s.id); + // Scenario 3: check if Network ACLs associated with the resource allow all traffic + var describeNetworkAcls = helpers.addSource(cache, source, + ['ec2', 'describeNetworkAcls', region]); + + if (!describeNetworkAcls || describeNetworkAcls.err || !describeNetworkAcls.data) { + helpers.addResult(results, 3, + `Unable to query for Network ACLs: ${helpers.addError(describeNetworkAcls)}`, region); + } else if (describeNetworkAcls.data.length && subnetIds) { + let naclDeny = true; + for (let subnetId of subnetIds) { + let instanceACL = describeNetworkAcls.data.find(acl => acl.Associations.find(assoc => assoc.SubnetId === subnetId)); + if (instanceACL && instanceACL.Entries && instanceACL.Entries.length) { + const allowRules = instanceACL.Entries.filter(entry => + entry.Egress === false && + entry.RuleAction === 'allow' && + (entry.CidrBlock === '0.0.0.0/0' || entry.Ipv6CidrBlock === '::/0') + ); + + const denyIPv4 = instanceACL.Entries.find(entry => + entry.Egress === false && + entry.RuleAction === 'deny' && + entry.CidrBlock === '0.0.0.0/0' + ); + + const denyIPv6 = instanceACL.Entries.find(entry => + entry.Egress === false && + entry.RuleAction === 'deny' && + entry.Ipv6CidrBlock === '::/0' + ); + + let exposed = allowRules.some(allowRule => { + return !instanceACL.Entries.some(denyRule => { + return ( + denyRule.Egress === false && + denyRule.RuleAction === 'deny' && + ( + (allowRule.CidrBlock && denyRule.CidrBlock === allowRule.CidrBlock) || + (allowRule.Ipv6CidrBlock && denyRule.Ipv6CidrBlock === allowRule.Ipv6CidrBlock) + ) && + denyRule.Protocol === allowRule.Protocol && + ( + denyRule.PortRange ? + (allowRule.PortRange && + denyRule.PortRange.From === allowRule.PortRange.From && + denyRule.PortRange.To === allowRule.PortRange.To) : true + ) && + denyRule.RuleNumber < allowRule.RuleNumber + ); + }); + }); + + // exposed - if NACL has an allow all rule + if (exposed) { + internetExposed += `, nacl ${instanceACL.NetworkAclId}`; + } + + // not exposed - if NACL has a deny rule + if (exposed || !denyIPv4 || !denyIPv6) { + naclDeny = false; + } + } else { + naclDeny = false; + } + } + + // not exposed - if all NACLs have deny rules + if (naclDeny) { + return ''; + } } + } } + // if there are no explicit allow or deny rules, we look at ELBs - // Scenario 3: check if Network ACLs associated with the resource allow all traffic - var describeNetworkAcls = helpers.addSource(cache, source, - ['ec2', 'describeNetworkAcls', region]); + if (elbs && elbs.length) { + for (const lb of elbs) { - if (!describeNetworkAcls || describeNetworkAcls.err || !describeNetworkAcls.data) { - helpers.addResult(results, 3, - `Unable to query for Network ACLs: ${helpers.addError(describeNetworkAcls)}`, region); - } else if (describeNetworkAcls.data.length && subnetId) { - let instanceACL = describeNetworkAcls.data.find(acl => acl.Associations.find(assoc => assoc.SubnetId === subnetId)); - if (instanceACL && instanceACL.Entries && instanceACL.Entries.length) { - - const allowRules = instanceACL.Entries.filter(entry => - entry.Egress === false && - entry.RuleAction === 'allow' && - (entry.CidrBlock === '0.0.0.0/0' || entry.Ipv6CidrBlock === '::/0') - ); - - - // Checking if there's a deny rule with lower rule number - let exposed = allowRules.some(allowRule => { - // Check if there's a deny with a lower rule number - return !instanceACL.Entries.some(denyRule => { - return ( - denyRule.Egress === false && - denyRule.RuleAction === 'deny' && - ( - (allowRule.CidrBlock && denyRule.CidrBlock === allowRule.CidrBlock) || - (allowRule.Ipv6CidrBlock && denyRule.Ipv6CidrBlock === allowRule.Ipv6CidrBlock) - ) && - denyRule.Protocol === allowRule.Protocol && - ( - denyRule.PortRange ? - (allowRule.PortRange && - denyRule.PortRange.From === allowRule.PortRange.From && - denyRule.PortRange.To === allowRule.PortRange.To) : true - ) && - denyRule.RuleNumber < allowRule.RuleNumber - ); - }); - }); - if (exposed) { - internetExposed += `> nacl ${instanceACL.NetworkAclId}`; - } else { - internetExposed = ''; + let isLBPublic = false; + if (lb.Scheme && lb.Scheme.toLowerCase() === 'internet-facing') { + if (lb.SecurityGroups && lb.SecurityGroups.length && describeSecurityGroups && + !describeSecurityGroups.err && describeSecurityGroups.data && describeSecurityGroups.data.length) { + let elbSGs = describeSecurityGroups.data.filter(sg => lb.SecurityGroups.includes(sg.GroupId)); + for (var elbSG of elbSGs) { + let exposedSG = checkSecurityGroup(elbSG, cache, region, false); + if (exposedSG) { + isLBPublic = true; + } + } + } + } + + if (isLBPublic) { + internetExposed += internetExposed.length ? `, elb ${lb.LoadBalancerName}`: `elb ${lb.LoadBalancerName}`; } } } @@ -1312,6 +1439,6 @@ module.exports = { processFieldSelectors: processFieldSelectors, checkNetworkInterface: checkNetworkInterface, checkNetworkExposure: checkNetworkExposure, - + getAttachedELBs: getAttachedELBs }; diff --git a/helpers/azure/functions.js b/helpers/azure/functions.js index 3fdc0a5652..4906bc5143 100644 --- a/helpers/azure/functions.js +++ b/helpers/azure/functions.js @@ -775,7 +775,7 @@ function checkSecurityGroup(securityGroups) { return {exposed: true}; } -function checkNetworkExposure(cache, source, networkInterfaces, securityGroups, region, results) { +function checkNetworkExposure(cache, source, networkInterfaces, securityGroups, location, results, lbNames) { let exposedPath = ''; if (securityGroups && securityGroups.length) { @@ -783,12 +783,35 @@ function checkNetworkExposure(cache, source, networkInterfaces, securityGroups, let exposedSG = checkSecurityGroup(securityGroups); if (exposedSG && exposedSG.exposed) { if (exposedSG.nsg) { - exposedPath += `nsg ${exposedSG.nsg}` + return `nsg ${exposedSG.nsg}` + } else { + return ''; } } + } - return exposedPath + if (lbNames && lbNames.length) { + const loadBalancers = shared.addSource(cache, source, + ['loadBalancers', 'listAll', location]); + + if (loadBalancers && !loadBalancers.err && loadBalancers.data && loadBalancers.data.length) { + let resourceLBs = loadBalancers.data.filter(lb => lbNames.includes(lb.id)); + if (resourceLBs && resourceLBs.length) { + for (let lb of resourceLBs) { + let isPublic = false; + if (lb.frontendIPConfigurations && lb.frontendIPConfigurations.length) { + isPublic = lb.frontendIPConfigurations.some(ipConfig => ipConfig.properties + && ipConfig.properties.publicIPAddress && ipConfig.properties.publicIPAddress.id); + if (isPublic && ((lb.nboundNatRules && nboundNatRules.length) || (lb.loadBalancingRules && lb.loadBalancingRules.length))) { + exposedPath += `lb ${lb.name}`; + break; + } + } + } + } + } } + return exposedPath; } module.exports = { @@ -803,7 +826,7 @@ module.exports = { remediateOpenPorts: remediateOpenPorts, remediateOpenPortsHelper: remediateOpenPortsHelper, checkMicrosoftDefender: checkMicrosoftDefender, - checkFlexibleServerConfigs: checkFlexibleServerConfigs, - checkNetworkExposure: checkNetworkExposure, + checkFlexibleServerConfigs:checkFlexibleServerConfigs, + checkNetworkExposure: checkNetworkExposure }; diff --git a/helpers/google/functions.js b/helpers/google/functions.js index babff8dd28..8a451b411a 100644 --- a/helpers/google/functions.js +++ b/helpers/google/functions.js @@ -411,7 +411,8 @@ function checkFirewallRules(firewallRules) { } const networkName = firewallRule.network ? firewallRule.network.split('/').pop() : ''; - let allSources = firewallRule.sourceRanges && firewallRule.sourceRanges.some(sourceAddressPrefix => + + let allSources = firewallRule.sourceRanges?.some(sourceAddressPrefix => sourceAddressPrefix === '*' || sourceAddressPrefix === '0.0.0.0/0' || sourceAddressPrefix === '::/0' || @@ -420,14 +421,13 @@ function checkFirewallRules(firewallRules) { sourceAddressPrefix.includes('/0') ); - if (allSources && firewallRule.allowed && firewallRule.allowed.some(allow => !!allow.IPProtocol)) { - return { exposed: true, networkName: `vpc ${networkName}` }; + if (allSources && firewallRule.allowed?.some(allow => !!allow.IPProtocol)) { + return {exposed: true, networkName: `vpc ${networkName}`}; } - - if (allSources && firewallRule.denied && firewallRule.denied.some(deny => deny.IPProtocol === 'all')) { - return { exposed: false }; + + if (allSources && firewallRule.denied?.some(deny => deny.IPProtocol === 'all')) { + return {exposed: false}; } - } return {exposed: true}; @@ -435,7 +435,122 @@ function checkFirewallRules(firewallRules) { } -function checkNetworkExposure(cache, source, networks, firewallRules, region, results) { +function getForwardingRules(cache, source, region, resource) { + let rules = []; + + let forwardingRules = getAllDataForService(cache, source, 'forwardingRules', 'list', region); + let backendServices = getAllDataForService(cache, source, 'backendServices', 'list', region); + let targetHttpProxies = getAllDataForService(cache, source, 'targetHttpProxies', 'list', region); + let targetHttpsProxies = getAllDataForService(cache, source, 'targetHttpsProxies', 'list', region); + let urlMaps = getAllDataForService(cache, source, 'urlMaps', 'list', region); + + if (!forwardingRules || !forwardingRules.length || !backendServices || !backendServices.length) { + return []; + } + + backendServices = backendServices.filter(service => { + if (service.backends && service.backends.length) { + return service.backends.some(backend => { + let group = backend.group.replace(/^.*?(\/projects\/.*)$/, '$1'); + return resource.selfLink.includes(group); + }); + } + }); + + if (backendServices && backendServices.length) { + forwardingRules.forEach(rule => { + let rulePath = `FR ${rule.name}`; + let targetProxyLink = ''; + let urlMapLink = ''; + let backendServiceLink = ''; + + if (rule.target && (rule.target.includes('targetHttpProxies') || rule.target.includes('targetHttpsProxies'))) { + let target = rule.target.replace(/^.*?(\/projects\/.*)$/, '$1'); + let targetProxy = rule.target.includes('targetHttpProxies') + ? targetHttpProxies.find(proxy => proxy.selfLink.includes(target)) + : targetHttpsProxies.find(proxy => proxy.selfLink.includes(target)); + + if (targetProxy) { + rulePath += ` > TP ${targetProxy.name}`; + targetProxyLink = targetProxy.selfLink; + + if (targetProxy.urlMap) { + let urlMap = urlMaps.find(map => map.selfLink.includes(targetProxy.urlMap.replace(/^.*?(\/projects\/.*)$/, '$1'))); + if (urlMap && urlMap.defaultService) { + rulePath += ` > UM ${urlMap.name}`; + urlMapLink = urlMap.selfLink; + + let serviceName = urlMap.defaultService.replace(/^.*?(\/projects\/.*)$/, '$1'); + let matchedBackendService = backendServices.find(service => service.selfLink.includes(serviceName)); + + if (matchedBackendService) { + rulePath += ` > BS ${matchedBackendService.name}`; + backendServiceLink = matchedBackendService.selfLink; + } + } + } else { + let matchedBackendService = backendServices.find(service => targetProxy.selfLink.includes(service.selfLink.replace(/^.*?(\/projects\/.*)$/, '$1'))); + + if (matchedBackendService) { + rulePath += ` > BS ${matchedBackendService.name}`; + backendServiceLink = matchedBackendService.selfLink; + } + } + } + } else if (rule.backendService) { + let serviceName = rule.backendService.replace(/^.*?(\/projects\/.*)$/, '$1'); + let matchedBackendService = backendServices.find(service => service.selfLink.includes(serviceName)); + + if (matchedBackendService) { + rulePath += ` > BS ${matchedBackendService.name}`; + backendServiceLink = matchedBackendService.selfLink; + } + } + + if (backendServiceLink) { + rules.push({ ...rule, rulePath }); + } + }); + } + return rules; +} + + +function getAllDataForService(cache, source, service, call, region) { + let allData = []; + + let globalData = shared.addSource(cache, source, [service, call, 'global']); + let regionalData = shared.addSource(cache, source, [service, call, region]); + + + if (globalData && !globalData.err && globalData.data && globalData.data.length) { + allData = allData.concat(globalData.data); + } + + if (regionalData && !regionalData.err && regionalData.data && regionalData.data.length) { + allData = allData.concat(regionalData.data); + } + return allData; +} + +function checkClusterExposure(cluster) { + const privateClusterConfig = cluster.privateClusterConfig || {}; + const masterAuthorizedNetworksConfig = cluster.masterAuthorizedNetworksConfig || {}; + + const cidrBlocks = masterAuthorizedNetworksConfig.cidrBlocks || []; + + const publicCidrPatterns = ['0.0.0.0/0', '::/0', '*', '::0']; + + const hasPublicCIDR = cidrBlocks.some(block => publicCidrPatterns.includes(block)); + + return ( + (!privateClusterConfig.enablePrivateEndpoint && privateClusterConfig.publicEndpoint) || // Public endpoint with no private endpoint + masterAuthorizedNetworksConfig.gcpPublicCidrsAccessEnabled || // Google Cloud public IPs are allowed + hasPublicCIDR // If there is a public CIDR like 0.0.0.0/0, ::/0, or * + ); +} + +function checkNetworkExposure(cache, source, networks, firewallRules, region, results, forwardingRules) { let exposedPath = ''; if (firewallRules && firewallRules.length) { @@ -445,6 +560,21 @@ function checkNetworkExposure(cache, source, networks, firewallRules, region, re if (isExposed.networkName) { return isExposed.networkName; } + } else { + return ''; + } + } + + // load balancing flow + if (forwardingRules && forwardingRules.length) { + for (let rule of forwardingRules) { + let ipAddress = rule.IPAddress; + if ((rule.loadBalancingScheme === 'EXTERNAL' || rule.loadBalancingScheme === 'EXTERNAL_MANAGED') && + (ipAddress && !ipAddress.startsWith('10.') && !ipAddress.startsWith('192.168.') && !ipAddress.startsWith('172.'))) { + exposedPath = rule.rulePath || rule.name; + break; + + } } } return exposedPath @@ -461,5 +591,8 @@ module.exports = { checkOrgPolicy: checkOrgPolicy, checkIAMRole: checkIAMRole, findOpenAllPortsEgress: findOpenAllPortsEgress, - checkNetworkExposure: checkNetworkExposure + checkNetworkExposure: checkNetworkExposure, + getForwardingRules: getForwardingRules, + checkClusterExposure: checkClusterExposure, + checkFirewallRules: checkFirewallRules }; diff --git a/plugins/aws/ec2/ec2NetworkExposure.js b/plugins/aws/ec2/ec2NetworkExposure.js index 371fc90093..47b969f9d8 100644 --- a/plugins/aws/ec2/ec2NetworkExposure.js +++ b/plugins/aws/ec2/ec2NetworkExposure.js @@ -10,10 +10,12 @@ module.exports = { more_info: 'EC2 instances exposed to the internet are at a higher risk of unauthorized access, data breaches, and cyberattacks. It’s crucial to limit exposure by securing access through proper configuration of security groups, NACLs, and route tables.', link: 'https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Security.html', recommended_action: 'Secure EC2 instances by restricting access with properly configured security groups and NACLs.', - apis: ['EC2:describeInstances', 'EC2:describeNetworkAcls', 'EC2:describeSecurityGroups', 'EC2:describeNetworkInterfaces', 'EC2:describeSubnets', 'EC2:describeRouteTables'], + apis: ['EC2:describeInstances', 'EC2:describeNetworkAcls', 'EC2:describeSecurityGroups', 'EC2:describeNetworkInterfaces', 'EC2:describeSubnets', + 'EC2:describeRouteTables', 'ELB:describeLoadBalancers','ELBv2:describeLoadBalancers', 'ELBv2:describeTargetGroups', 'ELBv2:describeTargetHealth', 'ELBv2:describeListeners'], realtime_triggers: ['ec2:RunInstances','ec2:TerminateInstances', 'ec2:CreateNetworkAcl', 'ec2:ReplaceNetworkAclEntry', 'ec2:ReplaceNetworkAclAssociation', 'ec2:DeleteNetworkAcl', 'ec2:CreateSecurityGroup', 'ec2:AuthorizeSecurityGroupIngress','ec2:ModifySecurityGroupRules','ec2:RevokeSecurityGroupIngress', - 'ec2:DeleteSecurityGroup', 'ec2:ModifyInstanceAttribute', 'ec2:ModifySubnetAttribute'], + 'ec2:DeleteSecurityGroup', 'ec2:ModifyInstanceAttribute', 'ec2:ModifySubnetAttribute', 'elasticloadbalancing:CreateLoadBalancer', 'elasticloadbalancing:ModifyTargetGroups', 'elasticloadbalancing:RegisterTarget', 'elasticloadbalancing:DeregisterTargets', 'elasticloadbalancing:DeleteLoadBalancer', + 'elasticloadbalancing:DeleteTargetGroup', 'elasticloadbalancing:RegisterInstancesWithLoadBalancer', 'elasticloadbalancing:DeregisterInstancesWithLoadBalancer','elasticloadbalancing:CreateListener', 'elasticloadbalancing:DeleteListener'], run: function(cache, settings, callback) { var results = []; @@ -43,7 +45,11 @@ module.exports = { for (var instance of instances.Instances) { const { InstanceId } = instance; const arn = `arn:${awsOrGov}:ec2:${region}:${OwnerId}:instance/${InstanceId}`; - let internetExposed = helpers.checkNetworkExposure(cache, source, instance.SubnetId, instance.SecurityGroups, region, results); + + // List all ELB's attached to the instance + let elbs = helpers.getAttachedELBs(cache, source, region, InstanceId, 'Instances', 'InstanceId'); + + let internetExposed = helpers.checkNetworkExposure(cache, source, [{id: instance.SubnetId}], instance.SecurityGroups, elbs, region, results, instance); if (internetExposed && internetExposed.length) { helpers.addResult(results, 2, `EC2 instance is exposed to the internet through ${internetExposed}`, region, arn); } else { diff --git a/plugins/aws/guardduty/eksProtectionEnabled.js b/plugins/aws/guardduty/eksProtectionEnabled.js index d39f143a18..befb261da5 100644 --- a/plugins/aws/guardduty/eksProtectionEnabled.js +++ b/plugins/aws/guardduty/eksProtectionEnabled.js @@ -39,6 +39,7 @@ module.exports = { } listDetectors.data.forEach(function(detectorId) { + var resource = 'arn:' + awsOrGov + ':guardduty:' + region + ':' + accountId + ':detector/' + detectorId; var getDetector = helpers.addSource(cache, source, ['guardduty', 'getDetector', region, detectorId]); if (!getDetector) return; @@ -49,20 +50,19 @@ module.exports = { } var detector = getDetector.data; - var resource = 'arn:' + awsOrGov + ':guardduty:' + region + ':' + accountId + ':detector/' + detector.detectorId; - if (detector.DataSources && - detector.DataSources.Kubernetes && - detector.DataSources.Kubernetes.AuditLogs && + if (detector.DataSources && + detector.DataSources.Kubernetes && + detector.DataSources.Kubernetes.AuditLogs && detector.DataSources.Kubernetes.AuditLogs.Status && detector.DataSources.Kubernetes.AuditLogs.Status.toLowerCase() === 'disabled'){ helpers.addResult(results, 2, 'GuardDuty EKS protection is disabled', region, resource); } else { helpers.addResult(results, 0, 'GuardDuty EKS protection is enabled', region, resource); - } - + } + }); - + rcb(); }, function(){ callback(null, results, source); diff --git a/plugins/azure/virtualmachines/vmNetworkExposure.js b/plugins/azure/virtualmachines/vmNetworkExposure.js index 4258c9954f..ccb42e09e1 100644 --- a/plugins/azure/virtualmachines/vmNetworkExposure.js +++ b/plugins/azure/virtualmachines/vmNetworkExposure.js @@ -10,8 +10,8 @@ module.exports = { more_info: 'Virtual machines exposed to the internet are at a higher risk of unauthorized access, data breaches, and cyberattacks. It’s crucial to limit exposure by securing access through proper configuration of security group and firewall rules.', link: 'https://learn.microsoft.com/en-us/azure/security/fundamentals/virtual-machines-overview', recommended_action: 'Secure VM instances by restricting access with properly configured security group and firewall rules.', - apis: ['virtualMachines:listAll', 'networkInterfaces:listAll', 'networkSecurityGroups:listAll', 'virtualNetworks:listAll'], - realtime_triggers: ['microsoftcompute:virtualmachines:write', 'microsoftnetwork:networkinterfaces:write', 'microsoftcompute:virtualmachines:delete', 'microsoftnetwork:networkinterfaces:delete', 'microsoftnetwork:networksecuritygroups:write','microsoftnetwork:networksecuritygroups:delete', 'microsoftnetwork:virtualnetworks:write','microsoftnetwork:virtualnetworks:delete'], + apis: ['virtualMachines:listAll', 'networkInterfaces:listAll', 'networkSecurityGroups:listAll', 'virtualNetworks:listAll', 'loadBalancers:listAll'], + realtime_triggers: ['microsoftcompute:virtualmachines:write', 'microsoftnetwork:networkinterfaces:write', 'microsoftcompute:virtualmachines:delete', 'microsoftnetwork:networkinterfaces:delete', 'microsoftnetwork:networksecuritygroups:write','microsoftnetwork:networksecuritygroups:delete', 'microsoftnetwork:virtualnetworks:write','microsoftnetwork:virtualnetworks:delete','microsoftnetwork:loadbalancers:write', 'microsoftnetwork:loadbalancers:delete'], run: function(cache, settings, callback) { var results = []; @@ -58,6 +58,7 @@ module.exports = { virtualMachines.data.forEach(virtualMachine => { let vm_interfaces = []; let securityGroups = []; + let loadBalancers = []; if (virtualMachine.networkProfile && virtualMachine.networkProfile.networkInterfaces && virtualMachine.networkProfile.networkInterfaces.length > 0) { let interfaceIDs = virtualMachine.networkProfile.networkInterfaces.map(nic => nic.id); @@ -83,8 +84,41 @@ module.exports = { } securityGroups = networkSecurityGroups.data.filter(nsg => securityGroupIDs.includes(nsg.id)); } + + // get load balancers + for (let nic of vm_interfaces) { + if (nic.ipConfigurations && nic.ipConfigurations.length) { + nic.ipConfigurations.map(ipConfig => { + if (ipConfig.properties) { + if (ipConfig.properties.loadBalancerInboundNatRules && ipConfig.properties.loadBalancerInboundNatRules.length) { + ipConfig.properties.loadBalancerInboundNatRules.forEach(rule => { + let id = rule.id; + let match = id.match(/\/subscriptions\/.+?(?=\/inboundNatRules)/); + + if (match && match[0]) { + if (!loadBalancers.includes(match[0])) { + loadBalancers.push(match[0]); + } + } + }); + } + if (ipConfig.properties.loadBalancerBackendAddressPools && ipConfig.properties.loadBalancerBackendAddressPools.length) { + ipConfig.properties.loadBalancerBackendAddressPools.forEach(pool => { + let id = pool.id; + let match = id.match(/\/subscriptions\/.+?(?=\/backendAddressPools)/); + if (match && match[0]) { + if (!loadBalancers.includes(match[0])) { + loadBalancers.push(match[0]); + } + } + }); + } + } + }); + } + } } - let internetExposed = helpers.checkNetworkExposure(cache, source, vm_interfaces, securityGroups, location, results); + let internetExposed = helpers.checkNetworkExposure(cache, source, vm_interfaces, securityGroups, location, results, loadBalancers); if (internetExposed && internetExposed.length) { helpers.addResult(results, 2, `VM is exposed to the internet through ${internetExposed}`, location, virtualMachine.id); } else { diff --git a/plugins/google/compute/instanceNetworkExposure.js b/plugins/google/compute/instanceNetworkExposure.js index 2c4e350e5c..99c8278c52 100644 --- a/plugins/google/compute/instanceNetworkExposure.js +++ b/plugins/google/compute/instanceNetworkExposure.js @@ -2,7 +2,6 @@ var async = require('async'); var helpers = require('../../../helpers/google'); const { instanceGroups, forwardingRules } = require('../../../helpers/google/regions'); - module.exports = { title: 'Network Exposure', category: 'Compute', @@ -12,8 +11,15 @@ module.exports = { more_info: 'Virtual machines exposed to the internet are at a higher risk of unauthorized access, data breaches, and cyberattacks. It’s crucial to limit exposure by securing access through proper configuration of network and firewall rules.', link: 'https://cloud.google.com/firewall/docs/firewalls', recommended_action: 'Secure VM instances by restricting access with properly configured security group and firewall rules.', - apis: ['compute:list', 'firewalls:list'], - realtime_triggers: ['compute.instances.insert', 'compute.instances.delete','compute.firewalls.insert', 'compute.firewalls.delete', 'compute.firewalls.patch'], + apis: ['instanceGroups:aggregatedList', 'compute:list', 'firewalls:list', 'instanceGroups:listInstances', 'urlMaps:list', 'targetHttpProxies:list', 'targetHttpsProxies:list', + 'forwardingRules:list', 'backendServices:list' + ], + realtime_triggers: ['compute.instances.insert', 'compute.instances.delete', 'compute.instances.update', 'compute.firewalls.insert', 'compute.firewalls.delete', 'compute.firewalls.patch', + 'compute.backendServices.insert', 'compute.backendServices.delete', 'compute.backendServices.patch', 'compute.instanceGroups.insert', 'compute.instanceGroups.delete', 'compute.instanceGroups.update', + 'compute.instanceGroups.addInstances', 'compute.instanceGroups.removeInstances', 'compute.urlMaps.insert', 'compute.urlMaps.delete', 'compute.urlMaps.update', 'compute.urlMaps.patch', + 'compute.targetHttpProxies.insert', 'compute.targetHttpProxies.delete', 'compute.targetHttpProxies.patch', 'compute.targetHttpsProxies.insert', 'compute.targetHttpsProxies.delete', 'compute.targetHttpsProxies.patch', + 'compute.forwardingRules.insert', 'compute.forwardingRules.delete', 'compute.forwardingRules.patch' + ], run: function(cache, settings, callback) { var results = []; @@ -83,8 +89,33 @@ module.exports = { return isNetworkMatch && isTagMatch && isServiceAccountMatch; }); + + networks = networks.map(network => network.split('/').pop()); - let internetExposed = helpers.checkNetworkExposure(cache, source, networks, firewallRules, region, results); + + // get all instance groups for instance + let instanceGroups = []; + + var instanceList = helpers.addSource(cache, source, + ['instanceGroups','listInstances', 'global']); + + if (instanceList && !instanceList.err && instanceList.data && instanceList.data.length) { + let groups = instanceList.data.filter(list => list.instance === instance.selfLink); + if (groups && groups.length) { + instanceGroups = groups.map(group => group.parent); + } + } + + + let forwardingRules = []; + if (instanceGroups && instanceGroups.length) { + instanceGroups.forEach(instanceGroup => { + let igForwardingRules = helpers.getForwardingRules(cache, source, region, instanceGroup); + forwardingRules = forwardingRules.concat(igForwardingRules); + }) + + } + let internetExposed = helpers.checkNetworkExposure(cache, source, networks, firewallRules, region, results, forwardingRules); let resource = helpers.createResourceName('instances', instance.name, project, 'zone', zone); @@ -106,4 +137,5 @@ module.exports = { callback(null, results, source); }); } -}; \ No newline at end of file +}; +