Skip to main content

Application Load Balancer Internet Exposure

Overview

This check identifies Application Load Balancers (ALBs) that are internet-facing and have security groups allowing unrestricted inbound access from the entire internet (0.0.0.0/0 or ::/0). While some ALBs legitimately need to be public, allowing unrestricted access increases your attack surface unnecessarily.

Risk

When an ALB accepts traffic from any IP address on the internet, you face these risks:

  • Credential stuffing attacks: Attackers can target your login endpoints with automated password guessing
  • Web application exploits: Exposed endpoints become targets for SQL injection, XSS, and other web attacks
  • Data exfiltration: Compromised applications can leak sensitive data to unauthorized parties
  • Denial of service: Unrestricted access makes it easier for attackers to overwhelm your application
  • Automated scanning: Bots continuously probe public endpoints for vulnerabilities

Remediation Steps

Prerequisites

You need:

  • AWS Console access with permissions to modify security groups
  • Knowledge of which IP ranges or sources legitimately need access to your ALB
Required IAM permissions (for administrators)

Your IAM user or role needs these permissions:

  • elasticloadbalancing:DescribeLoadBalancers
  • ec2:DescribeSecurityGroups
  • ec2:AuthorizeSecurityGroupIngress
  • ec2:RevokeSecurityGroupIngress

AWS Console Method

  1. Identify the affected ALB

    • Go to EC2 Console in us-east-1
    • Click Load Balancers in the left sidebar
    • Find the ALB flagged by Prowler
  2. Find the attached security group

    • Select the ALB
    • Click the Security tab
    • Note the security group ID (e.g., sg-0abc123def456)
    • Click the security group link to open it
  3. Review inbound rules

    • On the security group page, click the Inbound rules tab
    • Look for rules with Source 0.0.0.0/0 or ::/0
  4. Remove overly permissive rules

    • Click Edit inbound rules
    • Delete any rule that allows TCP traffic from 0.0.0.0/0 or ::/0 (unless this is intentional for a public-facing service)
    • Click Save rules
  5. Add restrictive rules instead

    • Click Edit inbound rules again
    • Click Add rule
    • For Type, choose the protocol (e.g., HTTPS)
    • For Source, enter the specific CIDR ranges that need access (e.g., your corporate IP range, a WAF, or CloudFront IP ranges)
    • Click Save rules

If your ALB must be public: Consider these alternatives instead of removing the rule:

  • Add AWS WAF to filter malicious traffic
  • Use CloudFront with AWS Shield for DDoS protection
  • Implement rate limiting
AWS CLI (optional)

Find ALBs with internet-facing configuration

aws elbv2 describe-load-balancers \
--region us-east-1 \
--query 'LoadBalancers[?Scheme==`internet-facing`].[LoadBalancerArn,LoadBalancerName,SecurityGroups]' \
--output table

Check security group rules for an ALB

First, get the security group IDs from the ALB:

aws elbv2 describe-load-balancers \
--region us-east-1 \
--names <your-alb-name> \
--query 'LoadBalancers[0].SecurityGroups' \
--output text

Then inspect the security group rules:

aws ec2 describe-security-groups \
--region us-east-1 \
--group-ids <security-group-id> \
--query 'SecurityGroups[0].IpPermissions'

Remove an overly permissive rule

To remove a rule allowing HTTPS from anywhere:

aws ec2 revoke-security-group-ingress \
--region us-east-1 \
--group-id <security-group-id> \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0

Add a restrictive rule

Replace with a rule allowing only your corporate IP range:

aws ec2 authorize-security-group-ingress \
--region us-east-1 \
--group-id <security-group-id> \
--protocol tcp \
--port 443 \
--cidr 10.0.0.0/8

Replace 10.0.0.0/8 with your actual trusted CIDR range.

CloudFormation (optional)

Secure security group for an ALB

This template creates a security group that only allows traffic from specific trusted sources:

AWSTemplateFormatVersion: '2010-09-09'
Description: Secure security group for Application Load Balancer

Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where the ALB resides

TrustedCidr:
Type: String
Description: CIDR range allowed to access the ALB (e.g., corporate IP range)
Default: 10.0.0.0/8

AllowCloudFront:
Type: String
Description: Whether to allow CloudFront IP ranges
AllowedValues:
- 'true'
- 'false'
Default: 'false'

Resources:
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Secure security group for ALB - restricted access
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref TrustedCidr
Description: HTTPS from trusted network
Tags:
- Key: Name
Value: alb-restricted-sg

Outputs:
SecurityGroupId:
Description: ID of the ALB security group
Value: !Ref ALBSecurityGroup
Export:
Name: !Sub '${AWS::StackName}-SecurityGroupId'

To use with CloudFront, you can reference the AWS-managed prefix list for CloudFront IPs instead of opening to 0.0.0.0/0:

  CloudFrontIngress:
Type: AWS::EC2::SecurityGroupIngress
Condition: AllowCloudFrontCondition
Properties:
GroupId: !Ref ALBSecurityGroup
IpProtocol: tcp
FromPort: 443
ToPort: 443
SourcePrefixListId: pl-3b927c52 # CloudFront prefix list for us-east-1
Description: HTTPS from CloudFront
Terraform (optional)

Secure security group for an ALB

variable "vpc_id" {
description = "VPC ID where the ALB resides"
type = string
}

variable "trusted_cidrs" {
description = "List of CIDR ranges allowed to access the ALB"
type = list(string)
default = ["10.0.0.0/8"]
}

variable "use_cloudfront" {
description = "Whether to allow traffic from CloudFront"
type = bool
default = false
}

# Security group with restricted access
resource "aws_security_group" "alb" {
name = "alb-restricted-sg"
description = "Secure security group for ALB - restricted access"
vpc_id = var.vpc_id

tags = {
Name = "alb-restricted-sg"
}
}

# Allow HTTPS from trusted CIDRs
resource "aws_security_group_rule" "https_trusted" {
for_each = toset(var.trusted_cidrs)

type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [each.value]
security_group_id = aws_security_group.alb.id
description = "HTTPS from trusted network"
}

# Allow traffic from CloudFront (optional)
data "aws_ec2_managed_prefix_list" "cloudfront" {
count = var.use_cloudfront ? 1 : 0
name = "com.amazonaws.global.cloudfront.origin-facing"
}

resource "aws_security_group_rule" "https_cloudfront" {
count = var.use_cloudfront ? 1 : 0

type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
prefix_list_ids = [data.aws_ec2_managed_prefix_list.cloudfront[0].id]
security_group_id = aws_security_group.alb.id
description = "HTTPS from CloudFront"
}

# Egress rule (allow all outbound)
resource "aws_security_group_rule" "egress" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.alb.id
description = "Allow all outbound traffic"
}

output "security_group_id" {
description = "ID of the ALB security group"
value = aws_security_group.alb.id
}

Deploy with:

terraform init
terraform plan -var="vpc_id=vpc-12345678"
terraform apply -var="vpc_id=vpc-12345678"

Verification

After updating the security group, verify the change:

  1. In the AWS Console:

    • Go to EC2 > Security Groups
    • Select the security group attached to your ALB
    • Click the Inbound rules tab
    • Confirm no rules have 0.0.0.0/0 or ::/0 as the source (unless intentional)
  2. Test access:

    • Verify your application still works from legitimate sources
    • Optionally, test that access is blocked from outside your trusted IP ranges
CLI verification commands

Check security group rules:

aws ec2 describe-security-groups \
--region us-east-1 \
--group-ids <security-group-id> \
--query 'SecurityGroups[0].IpPermissions[*].[IpProtocol,FromPort,ToPort,IpRanges[*].CidrIp,Ipv6Ranges[*].CidrIpv6]' \
--output table

Find any remaining open rules:

aws ec2 describe-security-groups \
--region us-east-1 \
--group-ids <security-group-id> \
--query 'SecurityGroups[0].IpPermissions[?contains(IpRanges[*].CidrIp, `0.0.0.0/0`) || contains(Ipv6Ranges[*].CidrIpv6, `::/0`)]'

If this returns an empty array [], no overly permissive rules exist.

Additional Resources

Notes

  • Public-facing applications: If your ALB genuinely serves public traffic (e.g., a customer-facing website), you may need to keep it open to the internet. In this case, add AWS WAF for application-layer protection and consider using CloudFront with AWS Shield for DDoS mitigation.

  • Use AWS WAF: For public ALBs, attach a Web ACL to filter malicious requests. WAF can block SQL injection, XSS, and known bad actors.

  • CloudFront prefix list: If you use CloudFront in front of your ALB, use the AWS-managed prefix list (com.amazonaws.global.cloudfront.origin-facing) instead of 0.0.0.0/0. This restricts access to only CloudFront edge locations.

  • Internal vs. internet-facing: If your ALB only needs to serve internal traffic, consider recreating it as an internal load balancer (Scheme: internal) rather than just restricting the security group.

  • IPv6 consideration: Remember to check for both IPv4 (0.0.0.0/0) and IPv6 (::/0) open rules. Both should be restricted.

  • Rate limiting: Even with restricted source IPs, implement rate limiting at the WAF or application level to protect against abuse from trusted sources.