On this page
Cloud tests usually start one of two ways: the client hands us a read-only role for a configuration review, or we find a key during another test and the rules let us see how far it reaches. Either way the first hour is the same four questions. Who am I, what can I do, where are the secrets, and is anyone watching?
Everything on this page is read-only, and where a command could print a secret we list names, never values. Changing anything in a client account needs explicit written permission for that change.
What to check first
- The identity behind the credentials, and which account, subscription or tenant it lives in.
- Every policy that applies to it: attached directly, through groups, and through roles it can assume.
- Where secrets sit: function environment variables, parameter stores, key vaults, instance user data, CI variables.
- What is public: storage buckets, snapshots, function URLs.
- Whether audit logging is on in every region and goes somewhere the account cannot delete.
AWS IAM enumeration and escalation paths
aws sts get-caller-identity
aws iam list-attached-user-policies --user-name ci-deploy
aws iam list-groups-for-user --user-name ci-deploy
aws iam get-account-authorization-details > auth-details.json
The full authorization dump needs read access to IAM, which a review role has. From it we look for escalation paths, not just broad policies: iam:PassRole together with permission to create compute, iam:CreatePolicyVersion, iam:AttachUserPolicy, and roles whose trust policy lets too many principals assume them.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:user/ci-deploy \
--action-names iam:PassRole iam:CreatePolicyVersion lambda:CreateFunction \
--query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output table
Fix. Grant the actions a workload uses and nothing more; IAM Access Analyzer reports unused permissions. Deny IAM changes to everyone outside the platform team with a service control policy, and use permission boundaries on any role that can create other roles.
AWS secrets in Lambda, user data and SSM
aws lambda list-functions --query 'Functions[].FunctionName' --output text
aws lambda get-function-configuration --function-name orders-sync \
--query 'keys(Environment.Variables || `{}`)'
aws ec2 describe-instance-attribute --instance-id i-0abc1234 --attribute userData \
--query 'UserData.Value' --output text | base64 -d \
| grep -Ein 'password|secret|token|api[_-]?key' | cut -d: -f1
aws ssm describe-parameters --query 'Parameters[].[Name,Type]' --output table
Database passwords and third party API keys in function environment variables are among the most common things we find. The commands print variable names and the line numbers of suspicious user data lines, so no secret value lands in our terminal history or session logs; the report names the variable and the function, and that is enough for the client to rotate it. Anyone with read access to the function configuration can read the values.
Fix. Keep secrets in Secrets Manager or as SecureString parameters, and have the function fetch them at start. Restrict lambda:GetFunctionConfiguration, lambda:GetFunction and lambda:ListFunctions the way you would restrict reading the secret itself, since all three return environment variable values.
AWS public exposure and CloudTrail
aws s3control get-public-access-block --account-id 111122223333
aws ec2 get-snapshot-block-public-access-state --region us-east-1
aws lambda list-function-url-configs --function-name orders-sync \
--query 'FunctionUrlConfigs[].[FunctionUrl,AuthType]'
aws cloudtrail describe-trails --query 'trailList[].[Name,IsMultiRegionTrail,IsOrganizationTrail]'
aws cloudtrail get-trail-status --name org-trail --query IsLogging
Snapshot public access blocking is a per-region setting, so we run that line for every region the account uses. A function URL with AuthType of NONE is reachable by anyone who has the URL. describe-trails shows that a trail exists; only get-trail-status shows whether it is logging.
Fix. Turn on S3 Block Public Access at the account level and snapshot public access blocking in every region. Use AWS_IAM auth on function URLs unless the function is meant to be public. Send an organization trail covering every region to a separate logging account that the workload accounts cannot write to or delete from.
Azure role assignments and escalation paths
az account show --query '{tenant:tenantId,subscription:name,user:user.name}'
az account list --query '[].{name:name,id:id}' -o table
az role assignment list --assignee <object-id> --all -o table
Owner and User Access Administrator are the obvious ones. Less obvious: Contributor on a virtual machine or automation account allows running code as that resource's managed identity, and ownership of an app registration or service principal lets you add credentials to it and act with its Graph permissions. Our Entra agent role research is a recent case of that ownership path.
Fix. No standing Owner assignments; use Privileged Identity Management for time-bound elevation. Review who owns each app registration with high Graph permissions, and give managed identities only the roles their workload needs.
Azure Key Vault, app settings and storage
az keyvault list -o table
az webapp config appsettings list --name orders-api --resource-group prod-rg --query '[].name' -o tsv
az storage account list --query '[].{name:name,publicBlob:allowBlobPublicAccess,sharedKey:allowSharedKeyAccess}' -o table
Fix. Use Key Vault references in app settings instead of values. Disable public blob access and shared key access on storage accounts that do not need them, and use Azure RBAC on Key Vault rather than access policies.
Azure activity log export
az monitor diagnostic-settings subscription list -o table
An empty result means the subscription's activity log is kept only for the platform's default retention and is not sent anywhere the client's security team queries.
Fix. Export the activity log from every subscription to a Log Analytics workspace or storage account in a separate subscription that workload owners cannot change.
We run atarus-cloud and Prowler for breadth once the manual pass has found the paths worth explaining.
What the defender sees
Every call above lands in CloudTrail or the Azure activity log under the identity we used. Enumeration from a CI key at an odd hour, from a new address, is a detection the client can build. If nothing fired, that goes in the report.