#!/usr/bin/env python3 # For authorized penetration testing and lab environments only """Cloud Penetration Testing Agent - Enumerates and tests AWS IAM misconfigurations.""" import json import logging import argparse import subprocess from datetime import datetime logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) def enumerate_iam_users(): """Enumerate all IAM users in the AWS account.""" cmd = ["aws", "iam", "list-users", "--output", "json"] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: users = json.loads(result.stdout).get("Users", []) logger.info("Enumerated %d IAM users", len(users)) return [{"username": u["UserName"], "arn": u["Arn"], "created": u["CreateDate"]} for u in users] return [] def enumerate_iam_roles(): """Enumerate IAM roles and identify cross-account trust relationships.""" cmd = ["aws", "iam", "list-roles", "--output", "json"] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: roles = json.loads(result.stdout).get("Roles", []) cross_account = [] for role in roles: policy_doc = role.get("AssumeRolePolicyDocument", {}) for statement in policy_doc.get("Statement", []): principal = statement.get("Principal", {}) aws_principal = principal.get("AWS", "") if isinstance(aws_principal, str) and ":root" in aws_principal: cross_account.append({ "role": role["RoleName"], "arn": role["Arn"], "trusted_account": aws_principal, }) logger.info("Found %d cross-account trust roles", len(cross_account)) return cross_account return [] def check_imds_v1_instances(): """Check for EC2 instances running with IMDSv1 (vulnerable to SSRF).""" cmd = [ "aws", "ec2", "describe-instances", "--query", "Reservations[*].Instances[*].[InstanceId,MetadataOptions.HttpTokens,State.Name]", "--output", "json", ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: instances = json.loads(result.stdout) vulnerable = [] for reservation in instances: for inst in reservation: if inst[1] == "optional" and inst[2] == "running": vulnerable.append({"instance_id": inst[0], "imds": "v1 (optional)", "state": inst[2]}) logger.info("Found %d instances with IMDSv1 enabled", len(vulnerable)) return vulnerable return [] def check_public_s3_buckets(): """Enumerate S3 buckets and check for public access.""" cmd = ["aws", "s3api", "list-buckets", "--query", "Buckets[*].Name", "--output", "text"] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: return [] buckets = result.stdout.strip().split() public_buckets = [] for bucket in buckets: status_cmd = ["aws", "s3api", "get-bucket-policy-status", "--bucket", bucket, "--output", "json"] r = subprocess.run(status_cmd, capture_output=True, text=True, timeout=120) if r.returncode == 0: policy_status = json.loads(r.stdout) if policy_status.get("PolicyStatus", {}).get("IsPublic", False): public_buckets.append(bucket) logger.warning("PUBLIC bucket found: %s", bucket) return public_buckets def check_lambda_env_secrets(): """Check Lambda functions for secrets in environment variables.""" cmd = ["aws", "lambda", "list-functions", "--query", "Functions[*].FunctionName", "--output", "text"] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: return [] functions = result.stdout.strip().split() findings = [] sensitive_keys = ["password", "secret", "key", "token", "api_key", "database_url", "connection_string"] for fn in functions: env_cmd = [ "aws", "lambda", "get-function-configuration", "--function-name", fn, "--query", "Environment.Variables", "--output", "json", ] r = subprocess.run(env_cmd, capture_output=True, text=True, timeout=120) if r.returncode == 0 and r.stdout.strip() != "null": env_vars = json.loads(r.stdout) exposed = [k for k in env_vars if any(s in k.lower() for s in sensitive_keys)] if exposed: findings.append({"function": fn, "exposed_keys": exposed}) logger.warning("Lambda %s has sensitive env vars: %s", fn, exposed) return findings def test_privesc_create_policy_version(policy_arn): """Test if iam:CreatePolicyVersion can be used for privilege escalation.""" cmd = [ "aws", "iam", "simulate-principal-policy", "--policy-source-arn", policy_arn, "--action-names", "iam:CreatePolicyVersion", "--output", "json", ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: eval_results = json.loads(result.stdout).get("EvaluationResults", []) for er in eval_results: if er.get("EvalDecision") == "allowed": logger.warning("Privesc possible: %s has iam:CreatePolicyVersion", policy_arn) return True return False def generate_report(users, cross_account_roles, imdsv1_instances, public_buckets, lambda_secrets): """Generate cloud penetration test findings report.""" report = { "timestamp": datetime.utcnow().isoformat(), "iam_users": len(users), "cross_account_trusts": cross_account_roles, "imdsv1_vulnerable_instances": imdsv1_instances, "public_s3_buckets": public_buckets, "lambda_env_secrets": lambda_secrets, "finding_count": ( len(cross_account_roles) + len(imdsv1_instances) + len(public_buckets) + len(lambda_secrets) ), } print(json.dumps(report, indent=2)) return report def main(): parser = argparse.ArgumentParser(description="Cloud Penetration Testing Agent") parser.add_argument("--profile", default="default", help="AWS CLI profile") parser.add_argument("--output", default="cloud_pentest_report.json") args = parser.parse_args() users = enumerate_iam_users() cross_account = enumerate_iam_roles() imdsv1 = check_imds_v1_instances() public_buckets = check_public_s3_buckets() lambda_secrets = check_lambda_env_secrets() report = generate_report(users, cross_account, imdsv1, public_buckets, lambda_secrets) with open(args.output, "w") as f: json.dump(report, f, indent=2) logger.info("Cloud pentest report saved to %s", args.output) if __name__ == "__main__": main()