Source: RhinoSecurityLabs/pacu @ e597f23

How It Works

Pacu is an interactive AWS penetration testing framework written in Python. It operates as a stateful CLI session manager that stores AWS credentials, enumerated data, and attack output in a local SQLite database. The framework ships with 70+ modules organized by attack phase — ENUM (reconnaissance), EXFIL (data theft), ESCALATE (privilege escalation), PERSIST (persistence), EXPLOIT (service exploitation), and EVADE (defense evasion). Every module follows a consistent interface: a module_info dict, an argparse parser, a main(args, pacu_main) entry point, and a summary() reporter. The pacu_main object provides helpers for printing, user input, boto3 client instantiation, and prerequisite data fetching — giving modules a uniform contract without having to manage their own database connections or credential injection.

The core design principle is offline-first enumeration: modules are encouraged to fetch data once via the AWS API, store it in the SQLite session, and then process it locally. This minimizes API calls (and associated CloudTrail logs) while giving attackers a rich local dataset to query with jq or the built-in data command.

Architecture

pacu/
 main.py                    # CLI REPL loop, command dispatch, session management
 settings.py                # Home dir (~/.local/share/pacu), DB path, log verbosity
 setup_database.py          # SQLAlchemy table creation
 core/
   models.py              # PacuSession + AWSKey SQLAlchemy models
   lib.py                 # Session dir, downloads dir, save() context manager
   enumerate_iam/
     main.py            # IAM permission enumerator (get_user, bruteforce)
     bruteforce_tests.py  # BRUTEFORCE_TESTS dict: 90+ services x list/list calls
     utils/
   secretfinder/          # Regex-based credential/secrets scanner
 modules/
   template.py            # Module authoring template
   service__action/       # One directory per module, main.py is entry point
   modules_archive/       # Deprecated/offline modules
 user_agents.txt            # Randomized user-agent strings for GuardDuty evasion

Data model: The PacuSession SQLAlchemy model has a fixed set of JSON-typed columns — one per AWS service (IAM, EC2, S3, Lambda, etc.) — plus a relationship to AWSKey objects. A migration system in models.py dynamically adds new columns via ALTER TABLE when modules register new service fields, preventing broken installs after updates.

The Spine

Entry point: cli.py → pacu/__main__.py → main.py’s Main class. The Main.__init__ loads all module categories, sets up the SQLAlchemy database, and registers signal handlers. The REPL loop reads commands from stdin, dispatches to handler methods, and logs every input/output to cmd_log.txt.

Key lifecycle for a module run:

  1. User types run iam__privesc_scan
  2. Main.exec_module() resolves the module name to a file path under pacu/modules/
  3. importlib.__import__() dynamically loads the module, calling module.main(parsed_args, self)
  4. The module uses pacu_main.get_active_session() to get the SQLAlchemy session and pacu_main.get_boto3_client('iam', region) to get a boto3 client with Pacu credentials injected
  5. Results are written to session.SERVICE_NAME JSON columns and files are saved under ~/.local/share/pacu/{session}/downloads/
  6. module.summary() returns a string displayed to the user

AWS credential flow: set_keys stores credentials in the AWSKey table. When a boto3 client is needed, get_boto3_client() constructs one with aws_access_key_id, aws_secret_access_key, and aws_session_token from the active key. The assume_role command wraps this with sts.assume_role() and stores the resulting temporary credentials as a new AWSKey.

Key Patterns

Module Convention

Every module follows an identical structure: a module_info dict (name, author, category, services, prerequisites, tab-completion hints), an argparse.ArgumentParser, a main(args, pacu_main) function, and a summary(data, pacu_main) that returns a ⇐1000-char string. The pacu_main object provides get_active_session(), get_boto3_client(), fetch_data(), install_dependencies(), and wrapper methods for print and input that also log to the session command history.

Permission Bruteforce

The BRUTEFORCE_TESTS dictionary maps 90+ AWS service names to lists of list_* and describe_* API calls. enumerate_iam() runs these in parallel using multiprocessing.dummy.Pool (threaded) against a shuffled list, catching ClientError to determine which calls succeeded. This is the wordlist architecture — not a separate file of strings, but a Python dict of API operation names embedded directly in the module.

Privilege Escalation Scanner

The iam__privesc_scan module defines 25+ escalation methods (AttachUserPolicy, CreateNewPolicyVersion, PassExistingRoleToNewLambdaThenInvoke, etc.) as structured data with info strings and execute functions. The scanner cross-references enumerated permissions against known attack patterns, presents findings interactively, and can execute selected methods — making it both a detector and an exploit tool.

Persistence via CloudTrail Events

The lambda__backdoor_new_users module deploys a Lambda function with a CloudWatch Events rule that triggers on CreateUser IAM events. The Lambda generates access keys for any newly created user and POSTs them to an attacker-controlled URL. This survives credential rotation because the trigger lives in AWS itself.

CloudFormation Resource Injection

The cfn__resource_injection module targets S3 buckets used for CloudFormation templates. An attacker with bucket write access sets up S3 → Lambda notifications. When a victim uploads a template, the Lambda modifies it to inject an admin IAM role before deployment — exploiting the ~1-second window between upload and deploy.

Detection Enumeration

The detection__enum_services module checks what is monitoring the environment: GuardDuty detectors, CloudTrail trails, Shield subscriptions, Config rules, CloudWatch alarms, VPC flow logs. This informs operational security decisions before running louder modules.

Non-Obvious Details

GuardDuty user-agent evasion in main.py: get_boto3_client() silently replaces the user agent with a random entry from user_agents.txt if it detects kali, parrot, or pentoo in the string — because GuardDuty generates findings for API calls made from those distros.

AWS CLI passthrough: The aws command prefix triggers shell execution. Pacu constructs temporary AWS_SHARED_CREDENTIALS_FILE and AWS_CONFIG_FILE entries so the subprocess uses Pacu’s active credentials, bridging the boto3-based module world with the AWS CLI ecosystem.

Secret censoring in logs: SecretAccessKey values are partially redacted in cmd_log.txt — the first half is shown, the rest are asterisks. This provides defense-in-depth for local filesystem exposure.

Double Ctrl+C interrupt handling (Issue #474): A two-second window tracks interrupt count. Single Ctrl+C stops the running module; double Ctrl+C exits Pacu entirely. Added because modules like cloudtrail__download_event_history can take hours.

SQLite schema migrations via ALTER TABLE: The migrations() function in models.py manually adds columns for any service in aws_data_field_names that does not yet exist in the table. This is pragmatic but fragile — it won’t handle column renames or deletions, and running migrations on a live database can race.

IAM NotAction parsing weakness: iam__privesc_scan explicitly warns it cannot correctly parse NotAction policy elements. Users with NotAction in their policies must manually verify results.

External dependency installation: Modules declare external_dependencies in module_info — Git URLs or single file URLs. pacu_main.install_dependencies() fetches and installs them at runtime, enabling modules to bring their own tooling without bundling it.

Assessment

Strengths: Pacu has a clean, stable module contract that makes it genuinely easy to write new modules. The session-based state management is well-designed — credentials, enumerated data, and attack output all live in one SQLite DB per engagement. The privilege escalation scanner is the best available open-source implementation of AWS IAM privesc path enumeration. The CLI is feature-complete with key rotation, AWS console URL generation, jq data queries, and command file replay.

Concerns: The SQLite schema management is the most fragile part — dynamic ALTER TABLE migrations can silently fail on edge cases. The BRUTEFORCE_TESTS wordlist requires manual curation as AWS adds services; there is no automated regeneration. The lambda__backdoor_new_users module depends on CloudTrail being active in us-east-1 — a hard constraint that silently fails without warning. Cleanup flags are opt-in and easy to forget, leaving forensic artifacts in the target environment. Error handling is inconsistent across modules.

Operational notes: detection__enum_services should be run first in any engagement to understand the monitoring posture. The IAM bruteforce module generates significant CloudTrail volume and should be used cautiously in production environments. The --cleanup flag in persistence modules should always be run after an engagement.

Warning

Pacu is a legitimate penetration testing tool. This analysis is for security research and authorized security assessments only. Unauthorized access to AWS environments is illegal.

Note

Related: analyzing-ghidra-mcp covers a complementary offensive tooling analysis in the same security/pentesting category.