AWS Security Hub provides automated compliance checks against the CIS AWS Foundations Benchmark, helping you identify security gaps and maintain compliance with industry best practices. This guide covers enabling the CIS standard, understanding controls, and remediating common findings.
This article is part of our comprehensive Cloud Security Tips for 2026 guide covering essential practices for protecting your cloud environment.
CIS Benchmark Sections
| Section | Focus Area | Control Count |
|---|---|---|
| 1 | Identity and Access Management | 21 controls |
| 2 | Logging | 14 controls |
| 3 | Monitoring | 14 controls |
| 4 | Networking | 5 controls |
| 5 | Storage | 4 controls |
Enable Security Hub and CIS Standard
Prerequisites
AWS Config must be enabled before Security Hub:
# Enable AWS Config
aws configservice put-configuration-recorder \
--configuration-recorder name=default,roleARN=arn:aws:iam::123456789012:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig \
--recording-group allSupported=true,includeGlobalResourceTypes=true
aws configservice put-delivery-channel \
--delivery-channel '{
"name": "default",
"s3BucketName": "config-bucket-123456789012",
"configSnapshotDeliveryProperties": {
"deliveryFrequency": "TwentyFour_Hours"
}
}'
aws configservice start-configuration-recorder \
--configuration-recorder-name defaultEnable Security Hub
# Enable Security Hub
aws securityhub enable-security-hub \
--enable-default-standards
# Or enable without default standards (add manually)
aws securityhub enable-security-hub \
--no-enable-default-standardsEnable CIS AWS Foundations Benchmark
# Enable CIS v1.4.0 standard
aws securityhub batch-enable-standards \
--standards-subscription-requests '[{
"StandardsArn": "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.4.0"
}]'
# List enabled standards
aws securityhub get-enabled-standards
# Check standard status
aws securityhub describe-standards-controls \
--standards-subscription-arn "arn:aws:securityhub:us-east-1:123456789012:subscription/cis-aws-foundations-benchmark/v/1.4.0"Multi-Account Setup
# From management account: Designate delegated admin
aws securityhub enable-organization-admin-account \
--admin-account-id 111122223333
# From delegated admin: Enable for all accounts
aws securityhub create-members \
--account-details '[
{"AccountId": "444455556666"},
{"AccountId": "777788889999"}
]'
# Auto-enable for new accounts
aws securityhub update-organization-configuration \
--auto-enable
# Configure central findings aggregation
aws securityhub create-finding-aggregator \
--region-linking-mode ALL_REGIONSView Compliance Status
Get Security Score
# Get overall security score
aws securityhub get-security-control-definitions
# Get compliance summary by standard
aws securityhub describe-standards-controls \
--standards-subscription-arn "arn:aws:securityhub:us-east-1:123456789012:subscription/cis-aws-foundations-benchmark/v/1.4.0" \
--query 'Controls[*].[ControlId,ControlStatus,SeverityRating]' \
--output tableView Failed Controls
# List failed CIS controls
aws securityhub get-findings \
--filters '{
"ComplianceStatus": [{"Value": "FAILED", "Comparison": "EQUALS"}],
"ProductName": [{"Value": "Security Hub", "Comparison": "EQUALS"}],
"GeneratorId": [{"Value": "cis-aws-foundations-benchmark", "Comparison": "PREFIX"}]
}' \
--query 'Findings[*].[Title,Compliance.Status,Severity.Label]' \
--output table
# Get detailed findings for specific control
aws securityhub get-findings \
--filters '{
"GeneratorId": [{"Value": "cis-aws-foundations-benchmark/v/1.4.0/1.4", "Comparison": "EQUALS"}]
}'Key CIS Controls and Remediation
Section 1: Identity and Access Management
1.4 - Ensure no root account access key exists
# Check for root access keys
aws iam get-account-summary \
--query 'SummaryMap.AccountAccessKeysPresent'
# Remediation: Delete root access keys via Console
# 1. Sign in as root user
# 2. Navigate to Security Credentials
# 3. Delete any access keys1.5 - Ensure MFA is enabled for root account
# Check root MFA status
aws iam get-account-summary \
--query 'SummaryMap.AccountMFAEnabled'
# Remediation: Enable MFA via Console
# Use hardware MFA or virtual MFA app1.10 - Ensure IAM password policy requires minimum length of 14
# Check current password policy
aws iam get-account-password-policy
# Set compliant password policy
aws iam update-account-password-policy \
--minimum-password-length 14 \
--require-symbols \
--require-numbers \
--require-uppercase-characters \
--require-lowercase-characters \
--allow-users-to-change-password \
--max-password-age 90 \
--password-reuse-prevention 241.16 - Ensure IAM policies are attached only to groups or roles
# Find users with directly attached policies
aws iam list-users --query 'Users[*].UserName' --output text | \
xargs -I {} aws iam list-attached-user-policies --user-name {}
# Remediation: Move policies to groups
aws iam add-user-to-group --user-name username --group-name groupname
aws iam detach-user-policy --user-name username --policy-arn arn:aws:iam::aws:policy/PolicyNameSection 2: Logging
2.1 - Ensure CloudTrail is enabled in all regions
# Check for multi-region trail
aws cloudtrail describe-trails \
--query 'trailList[*].[Name,IsMultiRegionTrail,IsLogging]'
# Create multi-region trail
aws cloudtrail create-trail \
--name organization-trail \
--s3-bucket-name cloudtrail-logs-bucket \
--is-multi-region-trail \
--include-global-service-events \
--enable-log-file-validation
aws cloudtrail start-logging --name organization-trail2.6 - Ensure S3 bucket access logging is enabled on CloudTrail S3 bucket
# Check bucket logging
aws s3api get-bucket-logging --bucket cloudtrail-logs-bucket
# Enable bucket logging
aws s3api put-bucket-logging \
--bucket cloudtrail-logs-bucket \
--bucket-logging-status '{
"LoggingEnabled": {
"TargetBucket": "access-logs-bucket",
"TargetPrefix": "cloudtrail-bucket-logs/"
}
}'Section 3: Monitoring
3.1-3.14 - CloudWatch alarms for API activity
# Create metric filter and alarm for unauthorized API calls (3.1)
aws logs put-metric-filter \
--log-group-name CloudTrail/DefaultLogGroup \
--filter-name UnauthorizedAPICalls \
--filter-pattern '{ ($.errorCode = "*UnauthorizedAccess*") || ($.errorCode = "AccessDenied*") }' \
--metric-transformations '[{
"metricName": "UnauthorizedAPICallsCount",
"metricNamespace": "CISBenchmark",
"metricValue": "1"
}]'
aws cloudwatch put-metric-alarm \
--alarm-name "CIS-3.1-UnauthorizedAPICalls" \
--metric-name UnauthorizedAPICallsCount \
--namespace CISBenchmark \
--statistic Sum \
--period 300 \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:123456789012:security-alerts
# Console sign-in without MFA (3.2)
aws logs put-metric-filter \
--log-group-name CloudTrail/DefaultLogGroup \
--filter-name ConsoleSignInWithoutMFA \
--filter-pattern '{ ($.eventName = "ConsoleLogin") && ($.additionalEventData.MFAUsed != "Yes") }' \
--metric-transformations '[{
"metricName": "ConsoleSignInWithoutMFACount",
"metricNamespace": "CISBenchmark",
"metricValue": "1"
}]'
# Root account usage (3.3)
aws logs put-metric-filter \
--log-group-name CloudTrail/DefaultLogGroup \
--filter-name RootAccountUsage \
--filter-pattern '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }' \
--metric-transformations '[{
"metricName": "RootAccountUsageCount",
"metricNamespace": "CISBenchmark",
"metricValue": "1"
}]'Section 4: Networking
4.1 - Ensure no security groups allow ingress from 0.0.0.0/0 to port 22
# Find security groups with open SSH
aws ec2 describe-security-groups \
--filters 'Name=ip-permission.from-port,Values=22' \
'Name=ip-permission.to-port,Values=22' \
'Name=ip-permission.cidr,Values=0.0.0.0/0' \
--query 'SecurityGroups[*].[GroupId,GroupName]'
# Remediation: Remove open rule
aws ec2 revoke-security-group-ingress \
--group-id sg-12345678 \
--protocol tcp \
--port 22 \
--cidr 0.0.0.0/04.3 - Ensure VPC flow logging is enabled in all VPCs
# List VPCs without flow logs
for vpc in $(aws ec2 describe-vpcs --query 'Vpcs[*].VpcId' --output text); do
flow_logs=$(aws ec2 describe-flow-logs --filter "Name=resource-id,Values=$vpc" --query 'FlowLogs[*].FlowLogId' --output text)
if [ -z "$flow_logs" ]; then
echo "VPC $vpc has no flow logs"
fi
done
# Enable flow logging
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids vpc-12345678 \
--traffic-type ALL \
--log-destination-type cloud-watch-logs \
--log-group-name VPCFlowLogs \
--deliver-logs-permission-arn arn:aws:iam::123456789012:role/FlowLogsRoleSection 5: Storage
5.1 - Ensure S3 bucket has block public access enabled
# Check account-level public access block
aws s3control get-public-access-block --account-id 123456789012
# Enable account-level block
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration '{
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}'Disable Controls
Disable controls that don't apply to your environment:
# Disable specific control
aws securityhub update-standards-control \
--standards-control-arn "arn:aws:securityhub:us-east-1:123456789012:control/cis-aws-foundations-benchmark/v/1.4.0/1.20" \
--control-status DISABLED \
--disabled-reason "Not applicable - using SSO for user management"
# Re-enable control
aws securityhub update-standards-control \
--standards-control-arn "arn:aws:securityhub:us-east-1:123456789012:control/cis-aws-foundations-benchmark/v/1.4.0/1.20" \
--control-status ENABLEDAutomated Remediation
Use Lambda and EventBridge for automatic remediation:
# EventBridge rule for specific CIS finding
aws events put-rule \
--name "RemediateOpenSecurityGroup" \
--event-pattern '{
"source": ["aws.securityhub"],
"detail-type": ["Security Hub Findings - Imported"],
"detail": {
"findings": {
"GeneratorId": [{"prefix": "cis-aws-foundations-benchmark/v/1.4.0/4.1"}],
"Compliance": {"Status": ["FAILED"]}
}
}
}'
# Lambda function for remediation
import boto3
def lambda_handler(event, context):
ec2 = boto3.client('ec2')
for finding in event['detail']['findings']:
sg_id = finding['Resources'][0]['Id'].split('/')[-1]
# Remove open SSH rule
try:
ec2.revoke_security_group_ingress(
GroupId=sg_id,
IpPermissions=[{
'IpProtocol': 'tcp',
'FromPort': 22,
'ToPort': 22,
'IpRanges': [{'CidrIp': '0.0.0.0/0'}]
}]
)
print(f"Remediated {sg_id}")
except Exception as e:
print(f"Error: {e}")Generate Compliance Reports
# Export findings to S3
aws securityhub create-insight \
--name "CIS-Compliance-Report" \
--filters '{
"GeneratorId": [{"Value": "cis-aws-foundations-benchmark", "Comparison": "PREFIX"}]
}' \
--group-by-attribute ComplianceStatus
# Get findings for reporting
aws securityhub get-findings \
--filters '{
"ProductName": [{"Value": "Security Hub", "Comparison": "EQUALS"}],
"GeneratorId": [{"Value": "cis-aws-foundations-benchmark", "Comparison": "PREFIX"}]
}' \
--query 'Findings[*].[GeneratorId,Compliance.Status,Severity.Label,Title]' \
--output json > cis-report.jsonBest Practices
| Practice | Recommendation |
|---|---|
| Version | Use CIS v1.4.0 for new implementations |
| Multi-Account | Use delegated administrator for centralized view |
| Baseline | Achieve 100% compliance before adding workloads |
| Exceptions | Document and justify disabled controls |
| Automation | Implement automated remediation for common findings |
| Monitoring | Track compliance score trends over time |
| Reviews | Conduct monthly compliance reviews |
Compliance Score Targets
| Environment | Target Score | Timeline |
|---|---|---|
| Development | 80%+ | Ongoing |
| Staging | 90%+ | Before production |
| Production | 95%+ | Maintain continuously |
| Regulated | 100% | Required for compliance |