Stormspotter Documentation

repository·main·Indexed 23 days ago

https://github.com/azure/stormspotter

Stormspotter is a tool for creating attack graphs of Azure subscription resources to help red teams visualize attack surfaces and defenders prioritize incident response. It includes Stormcollector for enumerating Azure resources via Azure CLI or Service Principal, a FastAPI backend for processing data into a Neo4j graph database, and a Vue/Quasar frontend for visualization.

Tokens
6.2K
Snippets
7
Records
36
Agent score
82%

What's inside Stormspotter

  1. Install Stormspotter without Docker

    main

    To run Stormspotter without Docker, you must have Python 3.8, NodeJS/npm, and Neo4j installed on your system.

    Backend Setup

    The backend uses FastAPI and is configured to run on port 9090 by default. If you change the port in backend/app.py (line 5), you must also update the port in the Q-Uploader component within frontend/src/components/DatabaseView.vue to ensure the frontend can reach the backend.

    cd backend
    python3 ssbackend.pyz

    Web App Setup

    The web app is a Vue/Quasar SPA located in frontend/dist/spa. Use the Quasar CLI to serve it:

    npm install -g @quasar/cli
    cd frontend/dist/spa
    quasar serve -p 9091 --history
  2. Install Stormspotter via Docker

    main

    The recommended installation method is using Docker Compose. This setup creates three containers: the Stormspotter Frontend, the Stormspotter Backend, and Neo4j v4.

    Default Ports:

    • Frontend UI: 9091
    • Neo4j HTTP: 7474
    • Neo4j Bolt: 7687

    Default Neo4j Credentials:

    • Username: neo4j
    • Password: password

    Note: You can change the Neo4j password by setting the NEO4JAUTH environment variable in the docker-compose file. Currently, Stormspotter only supports running these containers locally; remote frontend uploads are not supported.

    git clone https://github.com/Azure/Stormspotter
    docker-compose up
  3. Use Stormcollector to enumerate Azure resources

    main

    Stormcollector enumerates Azure subscriptions based on provided credentials. The recommended way to run it is using the pre-packaged sscollector.pyz file from the official releases.

    Authentication Types

    • Azure CLI: Requires you to run az login on your machine first.
    • Service Principal: Requires a Client ID and Client Secret.

    Basic Usage Examples

    Using Azure CLI credentials:

    python3 sscollector.pyz cli

    Using a Service Principal:

    python3 sscollector.pyz spn -t <tenant> -c <clientID> -s <clientSecret>

    Uploading Results

    Once Stormcollector finishes, it produces a SQLite file. To visualize the attack graph:

    1. Open the Stormspotter UI.
    2. Navigate to the Database tab.
    3. Locate the Stormcollector Upload section.
    4. Upload your SQLite file to begin processing.
    # Recommended: Use the PYZ package
    cd stormcollector
    python3 sscollector.pyz cli
    
    # Alternative: Install via pipenv
    cd stormcollector
    python3 -m pip install pipenv
    pipenv install .
    python3 ./sscollector.py
  4. Understand the ARM data collection lifecycle

    main

    The query_arm function follows a specific sequence to collect data across an Azure environment:

    1. Tenant Discovery: Lists all tenants available to the provided credentials.
    2. Subscription Filtering: For each tenant, it identifies subscriptions, applying subs and nosubs filters.
    3. Management Certs: Asynchronously queries for subscription management certificates (stored in certs.sqlite).
    4. RBAC Enumeration: Uses a ThreadPoolExecutor to enumerate role assignments and definitions (stored in rbac.sqlite).
    5. AAD Backfill (Optional): If args.azure and args.backfill are enabled, it performs an AAD backfill using the principals found in the RBAC step.
    6. Resource & Group Enumeration: Asynchronously queries each subscription for its resource groups and individual resources (stored in tenant.sqlite and {subscription_id}.sqlite).
  5. Configure cloud environments and endpoints

    main

    Stormcollector supports several Azure cloud environments. You can specify these via args.cloud. If a configuration file is provided via args.config, custom endpoints can be defined under an [ENDPOINTS] section.

    Standard Cloud Mappings:

    • PUBLIC: Standard Azure Public Cloud.
    • GERMAN: Azure Germany.
    • CHINA: Azure China.
    • USGOV: Azure US Government.

    Custom Endpoints via Config File: If using a config file, the following keys under [ENDPOINTS] are used to override defaults:

    • Resource_Manager (maps to ARM)
    • AD (maps to AD)
    • AD_Graph_ResourceId (maps to AAD)
    • MS_Graph (maps to GRAPH)
    • Management (maps to MGMT)
  6. Run the Stormspotter backend with uvicorn

    main
    The Stormspotter backend can be started by executing the main() function in backend/app.py. This uses uvicorn to serve the application located at backend.main:app. By default, it listens on all interfaces (0.0.0.0) at port 9090 with reload disabled.
  7. Deploy Stormspotter using Docker Compose

    main

    Stormspotter can be deployed as a multi-container application using Docker Compose. The setup includes a Neo4j database, a backend service, and a frontend web application, all connected via a bridge network named stormspotter-network.

    version: "3"
    
    networks:
      stormspotter-network:
        driver: "bridge"
    
    services:
      stormspotter-neo4j:
        image: neo4j:latest
        restart: unless-stopped
        networks:
          - stormspotter-network
        ports:
          - 7474:7474
          - 7687:7687
        environment:
          - NEO4J_dbms_memory_pagecache_size=1G
          - NEO4J_dbms_memory_heap.initial_size=1G
          - NEO4J_dbms_memory_heap_max__size=1G
          - NEO4J_AUTH=neo4j/password
    
      stormspotter-frontend:
        build:
          context: frontend
        depends_on:
          - stormspotter-neo4j
        networks:
          - stormspotter-network
        ports:
          - 9091:9091
    
      stormspotter-backend:
        tty: true
        build:
          context: backend
        depends_on:
          - stormspotter-neo4j
        networks:
          - stormspotter-network
        ports:
          - 9090:9090
        environment:
          - DOCKER_STORMSPOTTER=1
  8. Configure Stormspotter Neo4j service

    main

    The stormspotter-neo4j service uses the neo4j:latest image. It exposes ports 7474 (HTTP) and 7687 (Bolt).

    Key environment variables for configuration:

    • NEO4J_AUTH: Sets the authentication credentials (format: username/password). Default in this config is neo4j/password.
    • NEO4J_dbms_memory_pagecache_size: Configures the page cache size.
    • NEO4J_dbms_memory_heap.initial_size: Configures the initial heap size.
    • NEO4J_dbms_memory_heap_max__size: Configures the maximum heap size.
  9. Configure Stormspotter Backend service

    main

    The stormspotter-backend service is built from the backend directory and depends on the Neo4j service. It is accessible on port 9090.

    Key environment variables:

    • DOCKER_STORMSPOTTER=1: Enables Docker-specific mode for the backend.
  10. Stormcollector command line options reference

    main

    The following options are available for Stormcollector across all authentication types:

    OptionDescription
    --cloudSpecify a different Azure Cloud (GERMAN, CHINA, USGOV)
    --configSpecify a custom configuration for cloud environments
    --azureOnly enumerate Azure Resource Manager resources
    --aadOnly enumerate Azure Active Directory
    --subsSubscriptions to scan (space-delimited list)
    --nosubsSubscriptions to exclude (space-delimited list)
    --jsonConvert SQLite output to JSON (WARNING: Stormspotter only parses SQLite format)
    --ssl-certSpecify an SSL cert for Stormcollector to use for requests
    --backfillPerform AAD enumeration only for object IDs associated with RBAC enumeration (requires --azure)
  11. Query Azure AD objects with query_objects()

    main

    The query_objects method on an AADObject subclass performs the actual enumeration.

    • Full Enumeration: Calling query_objects() without arguments starts a full enumeration of the resource type, following odata.nextLink pagination if available.
    • Single Object Query: Passing an object_id (e.g., a UUID) will query only that specific resource.

    Results are automatically parsed via the subclass's parse method and written to a SQLite database in the OUTPUT_FOLDER using the format {ClassName}.sqlite.