PHP2Shell alert: critical PHP flaws CVE-2026-109618 / 109619 — check your servers and get patched fast
HashTech Systems
Cybersecurity

AWS Security Baseline Checklist: 12 Controls and the CLI Commands to Check Them

An AWS security baseline: GuardDuty, Inspector, Security Hub, CloudTrail, Config, Access Analyzer, S3 Block Public Access and root MFA, with CLI checks.

HashTech Engineering · October 26, 2026 · 7 min read

Most AWS accounts we look at were not set up badly on purpose. They grew. Someone launched an EC2 instance for a project in 2019, a contractor created an IAM user with an access key, a bucket was opened up for a demo and never closed. None of the services that would have flagged those things were switched on, because nobody was asked to switch them on.

This checklist is the baseline we expect every production AWS account to have. Each item comes with a read-only AWS CLI command so you can check it yourself in a few minutes. Nothing here changes your account. Where a fix is needed, the change is described separately so you can review it first.

Before you start: credentials and Regions

Run these checks with a read-only identity. The AWS managed policies SecurityAudit or ReadOnlyAccess are enough for everything below. Do not use root, and do not use an admin key that lives on a laptop. You need AWS CLI v2 and your account ID:

bash
aws --version
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
echo "$ACCOUNT"

The most common mistake in a self-audit is checking only one Region. GuardDuty, Inspector, Security Hub, AWS Config, IAM Access Analyzer and EBS encryption-by-default are all Regional: enabling them in us-east-1 does nothing for eu-west-2. Attackers who get hold of a key often launch resources in Regions you never use, precisely because nobody is watching there. List the Regions enabled for your account and loop over them:

bash
REGIONS=$(aws ec2 describe-regions --query "Regions[].RegionName" --output text)
echo $REGIONS

IAM and the account-level S3 Block Public Access setting are global, so those are checked once.

1. Root user has MFA and no access keys

The root user can do anything, including closing the account. It should have MFA enabled, no access keys, and should not be used for daily work. get-account-summary reports both:

bash
aws iam get-account-summary \
  --query "SummaryMap.{RootMFA:AccountMFAEnabled,RootAccessKeys:AccountAccessKeysPresent}"

You want RootMFA to be 1 and RootAccessKeys to be 0. If you use AWS Organizations with centralized root access management and have removed root credentials from member accounts, review those accounts in that context rather than adding MFA to a credential that no longer exists.

2. No long-lived IAM user keys you cannot explain

Access keys on IAM users are the most common way AWS accounts get compromised. The credential report lists every user, when each key was last used, and whether MFA is on. Generating the report is a harmless request, and it is included in the SecurityAudit policy:

bash
aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 --decode > credential-report.csv

Look for active keys older than 90 days, keys never used, and console users without MFA. Prefer IAM Identity Center for people and IAM roles for workloads.

3. CloudTrail is logging in every Region

Without CloudTrail you cannot answer the first question in any incident: who did what, and when. You want at least one multi-Region trail (or an organization trail) that is actually logging, with log file validation on:

bash
aws cloudtrail describe-trails \
  --query "trailList[].{Name:Name,Home:HomeRegion,MultiRegion:IsMultiRegionTrail,Org:IsOrganizationTrail,Validation:LogFileValidationEnabled,ARN:TrailARN}" \
  --output table

# For each trail, confirm it is logging and delivering
aws cloudtrail get-trail-status --name <trail-arn> \
  --query "{IsLogging:IsLogging,LatestDeliveryError:LatestDeliveryError}"

Pass the trail ARN rather than the name: for organization trails viewed from a member account, and for trails created in another Region, get-trail-status requires the full ARN. A trail that exists but has IsLogging: false or a LatestDeliveryError (usually a broken bucket policy) is not protecting you.

4. GuardDuty is enabled in every Region

GuardDuty is AWS's managed threat detection service. It reads CloudTrail, VPC flow and DNS logs and flags things like crypto-mining, credential use from unusual locations and instances talking to known-bad hosts. An empty DetectorIds list means it is off in that Region:

bash
for r in $REGIONS; do
  echo "== $r: $(aws guardduty list-detectors --region "$r" --query DetectorIds --output text)"
done

# Inspect one detector and its protection plans
aws guardduty get-detector --region us-east-1 --detector-id <detector-id> \
  --query "{Status:Status,Features:Features[].[Name,Status]}"

5. Amazon Inspector is scanning EC2, ECR and Lambda

Inspector continuously scans EC2 instances, container images in ECR and Lambda functions for known software vulnerabilities. After an incident like PHP2Shell, it is one of the fastest ways to find which instances carry a vulnerable package, provided it was already on. Check overall and per-resource status in each Region:

bash
for r in $REGIONS; do
  echo "== $r"
  aws inspector2 batch-get-account-status --region "$r" --account-ids "$ACCOUNT" \
    --query "accounts[].{State:state.status,EC2:resourceState.ec2.status,ECR:resourceState.ecr.status,Lambda:resourceState.lambda.status}" \
    --output table
done

6. Security Hub is enabled

Security Hub has changed shape. The original service, which runs automated best-practice checks against standards such as the AWS Foundational Security Best Practices and CIS benchmarks, is now called AWS Security Hub CSPM. The name AWS Security Hub now refers to a newer, unified service, generally available since December 2025, that correlates findings from Security Hub CSPM, Inspector, GuardDuty and Macie into prioritized "exposures". Both are still managed through the securityhub CLI namespace, and both are Regional. Check each:

bash
for r in $REGIONS; do
  echo "== $r"
  # Security Hub CSPM (posture checks against standards)
  aws securityhub describe-hub --region "$r" --query "{Hub:HubArn,Since:SubscribedAt}" 2>&1 | head -5
  # Newer unified Security Hub
  aws securityhub describe-security-hub-v2 --region "$r" --query "{Hub:HubV2Arn,Since:SubscribedAt}" 2>&1 | head -5
done

An error saying the account is not subscribed means the service is off in that Region. For how the pieces fit together, see GuardDuty, Inspector and Security Hub explained.

7. AWS Config is recording

Config records a history of resource configuration changes, which is what lets you answer "when did this security group open port 22 to the world?" Security Hub CSPM's checks also depend on Config being on. Check that a recorder exists and is recording in each Region:

bash
for r in $REGIONS; do
  echo "== $r"
  aws configservice describe-configuration-recorder-status --region "$r" \
    --query "ConfigurationRecordersStatus[].{Name:name,Recording:recording,LastStatus:lastStatus}" \
    --output table
done

8. IAM Access Analyzer has an active analyzer

Access Analyzer finds resources (S3 buckets, KMS keys, IAM roles, Lambda functions and others) that are shared with principals outside your account or organization. Analyzers are Regional, so you need one per Region you use:

bash
for r in $REGIONS; do
  echo "== $r"
  aws accessanalyzer list-analyzers --region "$r" \
    --query "analyzers[].{Name:name,Type:type,Status:status}" --output table
done

You want at least one analyzer of type ACCOUNT or ORGANIZATION with status ACTIVE. The unused-access analyzer types are useful for cleaning up stale permissions but are a separate feature.

9. S3 Block Public Access is on at the account level

Account-level Block Public Access overrides bucket settings and stops anyone from accidentally making a bucket public. All four settings should be true:

bash
aws s3control get-public-access-block --account-id "$ACCOUNT"

If this returns a NoSuchPublicAccessBlockConfiguration error, nothing is set at the account level. If you genuinely host public content in S3, serve it through CloudFront with origin access control instead of a public bucket.

10. EBS encryption by default, and encrypted RDS

EBS encryption-by-default makes every new volume and snapshot encrypted. It is a per-Region setting and does not encrypt existing volumes. For RDS, encryption is chosen at creation, so check what you already have:

bash
for r in $REGIONS; do
  echo "== $r: EBS default encryption = $(aws ec2 get-ebs-encryption-by-default --region "$r" --query EbsEncryptionByDefault --output text)"
  aws rds describe-db-instances --region "$r" \
    --query "DBInstances[].[DBInstanceIdentifier,StorageEncrypted]" --output text
done

11. Backups exist and are tested

Check that AWS Backup plans (or equivalent snapshot policies) cover your production data. Then actually restore something. A backup nobody has restored is a hope, not a control.

bash
aws backup list-backup-plans --query "BackupPlansList[].[BackupPlanName,LastExecutionDate]" --output table

12. Someone gets told when spending or alerts spike

A sudden bill is often the first sign of a stolen key. Make sure at least one budget with an alert exists, and that GuardDuty or Security Hub findings are routed somewhere a person reads (email, Slack or a ticket queue via EventBridge):

bash
aws budgets describe-budgets --account-id "$ACCOUNT" --query "Budgets[].BudgetName"
aws events list-rules --query "Rules[].[Name,State]" --output text

Turning findings into fixes

Once you have the results, sort them into three buckets: services that are simply off (quick, low-risk to enable), configuration that needs a decision (a public bucket someone might depend on, an old access key used by a cron job), and things that need a migration (unencrypted RDS instances, which need a snapshot-copy-restore). Enable detection services first: they change nothing about how your workloads run, and they start building the history you will want if something goes wrong.

If you run more than a couple of accounts, enable these through AWS Organizations with a delegated administrator rather than account by account. We cover that in GuardDuty, Inspector and Security Hub explained, and how we automate the evidence collection safely in automating security checks with AI agents.

HashTech Systems runs this checklist as part of its AWS Security Health Check; details are on our Cybersecurity page.

Let's Talk

Ready to build something that lasts?

Whether it's a charging network, an AI rollout, or a core enterprise system — let's talk about what you're building.