
Secure Your AWS Application Load Balancer: How to Drop Invalid Headers
Are you leveraging the power of AWS Application Load Balancers (ALBs) to manage traffic to your applications? While setting up health checks, SSL certificates, and security groups is essential, one often-overlooked setting can significantly enhance your application's security: drop invalid header fields. This article will guide you through enabling this feature and demonstrate its impact on your application's security posture.
Why You Should Care About Invalid HTTP Headers
HTTP headers are used in every request and response, acting as carriers for critical information, such as cookies, authorization tokens, and content types. However, malicious actors can exploit vulnerabilities by sending malformed or non-standard headers to your application, potentially leading to security breaches.
Enabling "Drop Invalid Headers" in Your ALB
The "drop invalid headers" feature is easy to enable and can significantly reduce your application's attack surface.
Here's how to enable it using Terraform and OpenTofu:
resource "aws_alb" "ALB" {
name = "my-load-balancer"
security_groups = [data.aws_security_group.alb_sg.id]
drop_invalid_header_fields = true
# ... other ALB configurations
}
By adding drop_invalid_header_fields = true
to your ALB configuration, you instruct the load balancer to filter out any headers that don't conform to HTTP standards.
Testing the "Drop Invalid Headers" Feature
To illustrate the functionality, let's create a simple ALB setup using Terraform. This ALB will initially return a fixed response for testing purposes.
Setting Up the ALB
This setup will include a VPC, public subnets, an Internet Gateway, route tables, and a security group allowing connections to the ELB.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-2"
}
module "vpc" {
source = "aws-ia/vpc/aws"
version = ">= 4.2.0"
name = "alb-example"
cidr_block = var.vpc_cidr
az_count = 2
subnets = {
public = {
netmask = 24
}
}
}
resource "aws_security_group" "alb_security_group" {
name = "alb-security-group"
vpc_id = module.vpc.vpc_attributes.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_alb" "sample" {
name = "sample-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_security_group.id]
subnets = values(module.vpc.public_subnet_attributes_by_az)[*].id
}
resource "aws_alb_listener" "listener" {
load_balancer_arn = aws_alb.sample.arn
port = "80"
protocol = "HTTP"
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/html"
message_body = "<html><body><h3>Fixed response!</h3></body></html>"
status_code = "200"
}
}
}
output "alb_dns_name" {
value = aws_alb.sample.dns_name
}
Sending Invalid Headers with cURL
Use cURL to send a request with both valid and invalid headers to the ALB:
Without the "drop invalid headers" setting enabled, the ALB forwards all headers to the backend.
Enabling "Drop Invalid Headers" and Re-testing
After enabling drop_invalid_header_fields = true
in your main.tf, apply the changes and execute the same cURL command. This time, the invalid header will be dropped, and your application will only receive the valid X-Custom-Header
.
Examining ALB Access Logs
While the ALB doesn't explicitly log dropped headers, you can analyze access logs to infer header filtering. By comparing the sizes of requests with and without invalid headers, you can determine which requests contained extra headers.
To enable ALB access logs:
- Create an S3 bucket to store the logs.
- Attach a bucket policy that allows the ELB to write logs to the bucket.
- Configure the ALB to send access logs to the S3 bucket.
locals {
elb_account_id = "652711504416" # ELB account ID (eu-west-2)
}
data "aws_caller_identity" "current" {}
resource "aws_s3_bucket" "logs_bucket" {
bucket = "logs-bucket-5535325"
}
resource "aws_s3_bucket_policy" "log_delivery_policy" {
bucket = aws_s3_bucket.logs_bucket.bucket
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${local.elb_account_id}:root",
"Service": "logdelivery.elasticloadbalancing.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::${aws_s3_bucket.logs_bucket.bucket}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"
}
]
}
EOF
}
resource "aws_alb" "sample" {
name = "sample-alb"
# ... other configurations
access_logs {
bucket = aws_s3_bucket.logs_bucket.bucket
enabled = true
}
}
Verifying Header Filtering with a Lambda Function
For a more definitive verification, use a Lambda function as the ALB's target. This function will receive all incoming headers and display them, allowing you to see exactly which headers are being passed through.
Lambda Function Code (Python)
Terraform Configuration for Lambda
resource "aws_iam_role" "lambda_role" {
name = "lambda_alb_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data "archive_file" "lambda_zip" {
type = "zip"
source_content = file("${path.module}/lambda.py")
source_content_filename = "lambda.py"
output_path = "${path.module}/lambda.zip"
}
resource "aws_lambda_function" "headers_lambda" {
filename = data.archive_file.lambda_zip.output_path
function_name = "display-request-headers"
role = aws_iam_role.lambda_role.arn
handler = "lambda.lambda_handler"
runtime = "python3.12"
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
timeout = 15
memory_size = 128
}
resource "aws_lambda_permission" "alb_invoke" {
statement_id = "AllowALBInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.headers_lambda.function_name
principal = "elasticloadbalancing.amazonaws.com"
}
resource "aws_lb_target_group" "lambda_tg" {
name = "lambda-headers-tg"
target_type = "lambda"
}
resource "aws_lb_target_group_attachment" "lambda_tg_attachment" {
target_group_arn = aws_lb_target_group.lambda_tg.arn
target_id = aws_lambda_function.headers_lambda.arn
depends_on = [aws_lambda_permission.alb_invoke]
}
resource "aws_alb" "sample" {
name = "sample-alb"
drop_invalid_header_fields = false
# ... other configurations
}
resource "aws_alb_listener" "listener" {
load_balancer_arn = aws_alb.sample.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.lambda_tg.arn
}
}
Testing with Lambda
- Disable
drop_invalid_header_fields
temporarily in yourmain.tf
to verify all headers are initially passed. - Update the listener to forward requests to the Lambda target group.
- Deploy the updated configuration.
- Send cURL requests with valid and invalid headers. The Lambda function will display all received headers, confirming that invalid headers are passed through when the setting is disabled.
- Enable
drop_invalid_header_fields
and redeploy.
Key Takeaways
- Enable "drop invalid headers": This simple setting can significantly improve your application's security posture by filtering out potentially malicious headers.
- Understand the limitations: This feature primarily protects your backend servers, not the ALB itself.
- Test thoroughly: Always test your configuration changes to ensure they don't negatively impact your application's functionality.
Enhance Your Load Balancer Security Today
Securing your AWS Application Load Balancer is crucial for protecting your applications from potential attacks. By enabling the "drop invalid headers" feature, you can proactively mitigate risks associated with malformed or malicious HTTP headers.