And how to check your own account for each one in about 15 minutes, using only the AWS CLI
Most small businesses on AWS are not hacked through some clever zero-day exploit. They are caught out by settings that took two minutes to get wrong and would take ten minutes to fix.
The numbers say this is not a big-company problem. The UK Government's Cyber Security Breaches Survey 2025/2026 estimates that 43% of UK businesses had a cyber security breach or attack in the previous 12 months, and among those that identified one, 88% said it involved phishing. Stolen passwords and leaked keys are how attackers get in.
And once they are in, they are rarely noticed quickly. IBM's 2025 Cost of a Data Breach findings for the UK put the average time to identify a breach at 148 to 168 days. That is around five months of someone else having access to your systems.
On AWS, the platform itself is rarely the weak point. Under the shared responsibility model, AWS secures the data centres, hardware and services. You are responsible for how your account is configured: who can sign in, what is open to the internet, and whether anyone would notice if something went wrong.
Here are the five mistakes I see most often, why each one matters, and exactly how to check for it.
First, a two-minute scorecard
Answer honestly. Each "no" or "not sure" is a gap.
- Does your AWS root user have MFA, and is it never used for day-to-day work?
- Are all IAM access keys less than 90 days old, and does every key belong to someone who still works with you?
- Are SSH, RDP and database ports closed to the whole internet?
- Is S3 Block Public Access turned on for the whole account?
- Would you get an alert if someone started using your account at 3am?
If you answered "yes" to all five, you are ahead of most small businesses. If not, read on.
Before you start: every command below is read-only. You need the AWS CLI and credentials with read access (the AWS-managed
SecurityAuditpolicy is enough). Some commands only cover the region your CLI is set to; there is a script at the end that checks every region at once.
1. The root user is unprotected, or still in use
Why it matters. The root user is the email address that created the AWS account. It can do anything, including closing the account or changing billing details, and no IAM policy can restrict it. In small companies it is common to find the root user shared between people, used for everyday work, or protected by nothing more than a password. With phishing behind most breaches, a stolen root password is the worst case.
How to check:
aws iam get-account-summary \
--query 'SummaryMap.{RootMFA: AccountMFAEnabled, RootAccessKeys: AccountAccessKeysPresent}'
You want RootMFA: 1 and RootAccessKeys: 0.
How to fix. Turn on MFA for the root user, ideally a hardware security key or passkey. Delete any root access keys. Then stop using root altogether: give each person their own sign-in, preferably through IAM Identity Center, and keep the root credentials somewhere safe for emergencies only.
2. Long-lived access keys that never expire
Why it matters. IAM access keys do not expire on their own. A key created three years ago for a deployment script, a developer's laptop or a contractor who has since left still works today. Keys also leak easily: committed to a Git repository, pasted into a chat, or left in an old .env file.
How to check. The IAM credential report lists every user, their keys, and when each key was last rotated and last used:
aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 --decode > credential-report.csv
(On older Macs, use base64 -D instead of base64 --decode.)
Open the CSV and look for keys that are active but were last rotated more than 90 days ago, keys that have never been used, and users who have no MFA or who no longer work with you.
How to fix. Delete keys nobody uses and rotate the rest. Better still, remove the need for them. People can sign in through IAM Identity Center with short-lived credentials, workloads on AWS can use IAM roles, and CI/CD pipelines such as GitHub Actions and GitLab can use OIDC federation instead of stored keys.
3. Servers and databases open to the whole internet
Why it matters. A security group rule that allows 0.0.0.0/0 on port 22 (SSH), 3389 (RDP), or a database port such as 3306 or 5432 means anyone on the internet can try to connect. Automated scanners find these within minutes. The rule is usually added "temporarily" to debug something, and never removed.
How to check:
aws ec2 describe-security-groups \
--filters Name=ip-permission.cidr,Values=0.0.0.0/0 \
Name=ip-permission.from-port,Values=22,3389,3306,5432 \
--query 'SecurityGroups[].[GroupId,GroupName]' --output table
Treat the result as a shortlist and open each group to confirm the rule. Check IPv6 rules (::/0) as well.
How to fix. Remove public access to admin and database ports. Use AWS Systems Manager Session Manager for shell access instead of SSH, which also gives you a log of every session. Keep databases in private subnets, and allow access only from the security groups that need it.
4. S3 buckets that are public by accident
Why it matters. Since April 2023, new S3 buckets block public access by default. But older buckets, and buckets where someone switched the protection off to share a file quickly, can still expose data to anyone with the link: customer exports, database backups, invoices.
How to check. First, see whether public access is blocked for the whole account:
aws s3control get-public-access-block \
--account-id "$(aws sts get-caller-identity --query Account --output text)"
If that returns an error, or any setting is false, check individual buckets:
aws s3api get-bucket-policy-status --bucket YOUR-BUCKET-NAME
"IsPublic": true means the bucket policy makes it public. IAM Access Analyzer in the console answers the same question for every bucket at once.
How to fix. Turn on Block Public Access at the account level, unless you have a specific reason not to. If you need to serve public files, such as website images, put them behind CloudFront with a private bucket.
5. No logging or alerting, so nobody would notice
Why it matters. This one is easy to miss because nothing looks broken. Without an audit trail and threat detection, an attacker using a leaked key, or someone deleting a database by mistake, can go unnoticed for months. Remember the IBM figure: around five months, on average, before a UK breach is even identified.
How to check:
# Is there a trail, is it logging, and does it cover every region?
aws cloudtrail describe-trails --query 'trailList[].[Name,IsMultiRegionTrail]' --output table
aws cloudtrail get-trail-status --name YOUR-TRAIL-NAME --query IsLogging
# Is GuardDuty turned on in this region? (An empty list means no.)
aws guardduty list-detectors
How to fix. Create a multi-region CloudTrail trail that writes to a dedicated S3 bucket, and turn on GuardDuty in every region you use. GuardDuty has a 30-day free trial, and the console shows an estimate of the monthly cost before the trial ends. Most importantly, send its findings to an email address someone actually reads.
Bonus: check every region in one go
Attackers like regions you never use, because nobody looks there. This loop checks GuardDuty and open SSH or RDP ports in every region enabled for your account:
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
echo "== $region"
echo -n " GuardDuty detectors: "
aws guardduty list-detectors --region "$region" --query 'DetectorIds' --output text
echo -n " Groups open on SSH/RDP: "
aws ec2 describe-security-groups --region "$region" \
--filters Name=ip-permission.cidr,Values=0.0.0.0/0 Name=ip-permission.from-port,Values=22,3389 \
--query 'SecurityGroups[].GroupId' --output text
done
An empty GuardDuty line means it is off in that region. Any group IDs on the second line are worth a closer look.
Your 15-minute plan
You do not need to fix everything this afternoon. In order of impact:
- Minutes 0–5: turn on MFA for the root user and delete any root access keys.
- Minutes 5–10: generate the credential report and deactivate keys that are unused or belong to people who have left.
- Minutes 10–15: turn on S3 Block Public Access for the account.
Then, this week: close public admin and database ports, and switch on CloudTrail and GuardDuty everywhere. Those two steps cover the most common way into a business, which is stolen credentials, and make sure you would actually notice if something happened.
Checking all of this automatically
The commands above work, but running them across every region, and remembering to repeat them every few months, is the hard part.
At KloudCare we built a free, read-only AWS assessment that checks all five of these, plus wasted spend and backup gaps, in about 10 minutes. It runs inside your own AWS account from a CloudFormation template you can read in full. It cannot change anything, and the report is saved to your own private S3 bucket. Nothing is sent to us unless you choose to share it.
👉 kloudcare.co.uk/assessment.html
Whichever way you check, treat each finding as something to confirm before changing. That open port might be the one your payment provider depends on, so talk to whoever owns it first.
KloudCare is a London-based AWS consultancy helping small and medium-sized businesses keep their AWS accounts cost-efficient, secure and recoverable. Find out more at kloudcare.co.uk.
Sources
- UK Government, Cyber Security Breaches Survey 2025/2026: https://www.gov.uk/government/statistics/cyber-security-breaches-survey-20252026/cyber-security-breaches-survey-20252026
- IBM UK, Cost of a Data Breach Report 2025: https://uk.newsroom.ibm.com/2025-cost-of-data-breach-UK
- AWS, Shared Responsibility Model: https://aws.amazon.com/compliance/shared-responsibility-model/
- AWS, Amazon S3 Block Public Access default for new buckets (April 2023): https://aws.amazon.com/about-aws/whats-new/2022/12/amazon-s3-automatically-enable-block-public-access-disable-access-control-lists-buckets-april-2023/
Top comments (0)