Skip to main content

Detach AWSCloudShellFullAccess Policy from IAM Identities

Overview

This check identifies IAM users, groups, and roles that have the AWS managed policy AWSCloudShellFullAccess attached. AWS CloudShell is a browser-based shell that lets you run commands directly in your AWS account. The AWSCloudShellFullAccess policy grants unrestricted access to CloudShell, including the ability to transfer files in and out.

Following the principle of least privilege, this broad policy should not be attached to IAM identities unless absolutely necessary.

Risk

When an IAM identity has full CloudShell access, a compromised account could:

  • Exfiltrate sensitive data via CloudShell's file download feature or internet access
  • Upload malicious tools and run them from within your AWS environment
  • Bypass endpoint detection by operating from AWS IP addresses instead of corporate networks
  • Persist access by storing artifacts in the CloudShell environment

This is a medium severity finding because it provides an easy path for data theft if credentials are compromised.

Remediation Steps

Prerequisites

You need permission to view and modify IAM policies (e.g., IAMFullAccess or equivalent).

AWS Console Method

  1. Sign in to the AWS IAM Console
  2. In the left navigation, click Policies
  3. Search for AWSCloudShellFullAccess
  4. Click on the policy name to open it
  5. Select the Entities attached tab
  6. Review the list of users, groups, and roles with this policy
  7. Select each entity that should not have full CloudShell access
  8. Click Detach and confirm

Tip: Before detaching, consider whether the user needs any CloudShell access. If so, create a custom policy with narrower permissions (see the Notes section below).

AWS CLI Method

Step 1: Find all entities with the policy attached

aws iam list-entities-for-policy \
--policy-arn arn:aws:iam::aws:policy/AWSCloudShellFullAccess \
--region us-east-1

This returns JSON listing all users, groups, and roles with the policy attached.

Step 2: Detach the policy from users

For each user in the output:

aws iam detach-user-policy \
--user-name <user-name> \
--policy-arn arn:aws:iam::aws:policy/AWSCloudShellFullAccess \
--region us-east-1

Step 3: Detach the policy from groups

For each group in the output:

aws iam detach-group-policy \
--group-name <group-name> \
--policy-arn arn:aws:iam::aws:policy/AWSCloudShellFullAccess \
--region us-east-1

Step 4: Detach the policy from roles

For each role in the output:

aws iam detach-role-policy \
--role-name <role-name> \
--policy-arn arn:aws:iam::aws:policy/AWSCloudShellFullAccess \
--region us-east-1

Automated Script

To detach the policy from all entities at once:

POLICY_ARN="arn:aws:iam::aws:policy/AWSCloudShellFullAccess"

# Detach from all users
aws iam list-entities-for-policy --policy-arn "$POLICY_ARN" \
--query 'PolicyUsers[].UserName' --output text | tr '\t' '\n' | \
while read user; do
[ -n "$user" ] && aws iam detach-user-policy \
--user-name "$user" --policy-arn "$POLICY_ARN"
done

# Detach from all groups
aws iam list-entities-for-policy --policy-arn "$POLICY_ARN" \
--query 'PolicyGroups[].GroupName' --output text | tr '\t' '\n' | \
while read group; do
[ -n "$group" ] && aws iam detach-group-policy \
--group-name "$group" --policy-arn "$POLICY_ARN"
done

# Detach from all roles
aws iam list-entities-for-policy --policy-arn "$POLICY_ARN" \
--query 'PolicyRoles[].RoleName' --output text | tr '\t' '\n' | \
while read role; do
[ -n "$role" ] && aws iam detach-role-policy \
--role-name "$role" --policy-arn "$POLICY_ARN"
done
CloudFormation Prevention

CloudFormation cannot directly detach AWS managed policies from existing resources. However, you can prevent future attachments by ensuring your CloudFormation templates do not include AWSCloudShellFullAccess.

Example of what NOT to do:

# BAD - Do not attach AWSCloudShellFullAccess
Resources:
MyRole:
Type: AWS::IAM::Role
Properties:
RoleName: MyRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSCloudShellFullAccess # Remove this line

Instead, use a restrictive custom policy:

Resources:
RestrictedCloudShellPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: RestrictedCloudShellAccess
Description: Limited CloudShell access without file transfer
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- cloudshell:CreateEnvironment
- cloudshell:GetEnvironmentStatus
- cloudshell:StartEnvironment
- cloudshell:StopEnvironment
Resource: '*'
- Effect: Deny
Action:
- cloudshell:GetFileDownloadUrls
- cloudshell:GetFileUploadUrls
- cloudshell:PutCredentials
Resource: '*'

MyRole:
Type: AWS::IAM::Role
Properties:
RoleName: MyRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- !Ref RestrictedCloudShellPolicy
Terraform Prevention

Similar to CloudFormation, Terraform is best used to prevent future attachments rather than modify existing resources outside of Terraform state.

Example of what NOT to do:

# BAD - Do not attach AWSCloudShellFullAccess
resource "aws_iam_role_policy_attachment" "cloudshell_full" {
role = aws_iam_role.my_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCloudShellFullAccess" # Remove this
}

Instead, use a restrictive custom policy:

resource "aws_iam_policy" "restricted_cloudshell" {
name = "RestrictedCloudShellAccess"
description = "Limited CloudShell access without file transfer"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"cloudshell:CreateEnvironment",
"cloudshell:GetEnvironmentStatus",
"cloudshell:StartEnvironment",
"cloudshell:StopEnvironment"
]
Resource = "*"
},
{
Effect = "Deny"
Action = [
"cloudshell:GetFileDownloadUrls",
"cloudshell:GetFileUploadUrls",
"cloudshell:PutCredentials"
]
Resource = "*"
}
]
})
}

resource "aws_iam_role_policy_attachment" "cloudshell_restricted" {
role = aws_iam_role.my_role.name
policy_arn = aws_iam_policy.restricted_cloudshell.arn
}

Verification

After detaching the policy, confirm no entities have it attached:

  1. Go to IAM > Policies in the AWS Console
  2. Search for AWSCloudShellFullAccess
  3. Click the policy and check the Entities attached tab
  4. The list should be empty (or contain only approved exceptions)
CLI Verification
aws iam list-entities-for-policy \
--policy-arn arn:aws:iam::aws:policy/AWSCloudShellFullAccess \
--region us-east-1

Expected output when compliant:

{
"PolicyGroups": [],
"PolicyUsers": [],
"PolicyRoles": []
}

Additional Resources

Notes

Creating a restricted CloudShell policy: If users need CloudShell access for legitimate purposes, consider creating a custom policy that:

  • Allows basic CloudShell operations (CreateEnvironment, StartEnvironment, StopEnvironment)
  • Denies file transfer capabilities (GetFileDownloadUrls, GetFileUploadUrls)
  • Denies credential injection (PutCredentials)

Service-linked roles: Some AWS services may attach this policy to service-linked roles. Review whether these are necessary and consider using Service Control Policies (SCPs) to prevent the policy from being attached at the organization level.

Compliance frameworks: This check maps to controls in:

  • CIS AWS Foundations Benchmark
  • PCI DSS
  • KISA-ISMS-P
  • NIS2
  • BSI C5