Post

OpenCTI

OpenCTI

OpenCTI

What is OpenCTI?

OpenCTI (Open Cyber Threat Intelligence) is an open-source platform designed to help organizations manage, structure, store, and share cyber threat intelligence (CTI). Think of it as a powerful, centralized knowledge base or a “digital brain” specifically for all things related to cybersecurity threats.

At its core, OpenCTI allows you to:

  1. Organize Information: Collect vast amounts of raw data (like threat reports, IOCs, malware analyses, news articles) from various sources.
  2. Structure Data: Standardize this information using the STIX 2.1 format. STIX (Structured Threat Information eXpression) is a language for describing CTI in a consistent and machine-readable way. This means you’re not just storing text; you’re storing defined objects like “Threat Actor,” “Malware,” “Indicator,” “Campaign,” “Vulnerability,” etc.
  3. Link Knowledge: The real power comes from its ability to create and visualize relationships between these objects. For example, you can link a specific “Threat Actor” to the “Malware” they use, the “Vulnerabilities” they exploit, the “Attack Patterns” (MITRE ATT&CK techniques) they employ, and the “Indicators” (IPs, domains, hashes) associated with their campaigns.
  4. Visualize & Analyze: Through its graph interface and analytical tools, you can explore these connections, understand attack lifecycles, and gain deeper insights into adversary operations.
  5. Share & Collaborate: Facilitate sharing of structured intelligence within your organization or with trusted partners.

It’s developed by Filigran, the same organization behind OpenBAS (Open Breach & Attack Simulation).

Why Use OpenCTI?

Organizations and security teams use OpenCTI to solve several common challenges in managing threat intelligence:

  1. Information Overload: Security teams are bombarded with data from countless sources (feeds, reports, news, internal alerts). OpenCTI provides a central place to consolidate and make sense of it all.
  2. Data Silos: Threat intelligence often lives in disparate systems: spreadsheets, email inboxes, vendor portals, individual analyst notes. This makes it hard to get a holistic view or correlate information. OpenCTI breaks down these silos.
  3. Lack of Standardization: Without a common language (like STIX), describing and sharing threat information is inefficient and prone to misinterpretation. OpenCTI enforces this standardization.
  4. Manual Processes: Manually ingesting, processing, and linking threat data is incredibly time-consuming and inefficient. OpenCTI, with its connectors, automates much of this.
  5. Difficulty Operationalizing Intelligence: It’s one thing to have intelligence; it’s another to make it actionable. OpenCTI helps bridge this gap by structuring data in a way that can inform defensive actions, detection rules, and strategic decisions.
  6. Improving Context: An isolated Indicator of Compromise (IOC) has limited value. OpenCTI helps you understand the context around that IOC: which threat actor uses it? What malware is it associated with? What campaign is it part of?

In essence, OpenCTI transforms raw threat data into structured, interconnected, and actionable intelligence, empowering security teams to better understand, anticipate, and respond to cyber threats.

Deploying OpenCTI with Docker: A Complete Guide Through Trial and Error

OpenCTI is a powerful, open-source threat intelligence platform. Deploying it can seem daunting, but by using Docker, we can create a robust and scalable instance. This guide provides a complete, step-by-step procedure for setting up OpenCTI version 6.6.14, complete with essential connectors like AlienVault and MITRE.

More importantly, this isn’t just a “happy path” guide. I’ll walk you through the real-world errors I encountered during my own setup, why they happened, and exactly how they were fixed. This troubleshooting journey is where the real learning happens!

Prerequisites

Before we begin, make sure you have the following installed and configured:

  • Docker Desktop: The setup relies entirely on Docker. Make sure it’s installed and running.
  • Allocate Resources: OpenCTI, especially its Elasticsearch component, needs memory. I highly recommend allocating at least 8GB of RAM to Docker Desktop. You can do this in Settings > Resources.
  • Git: Required for cloning repositories if you choose to, though not strictly necessary for this guide as we’ll create the files directly.

The Setup Procedure

Our setup will consist of three key files in a single project directory:

  1. docker-compose.yml: The blueprint for all our services.
  2. .env: A file to securely store all our secrets and configurations.
  3. rabbitmq.conf: A simple configuration file for our message queue.
git clone https://github.com/OpenCTI-Platform/docker.git

Define the Architecture with docker-compose.yml

This is the core of our setup. It defines every service, its connections, and its configuration. Copy the entire block below into your docker-compose.yml file.

services:
  redis:
    image: redis:7.4.3
    restart: always
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.18.0
    volumes:
      - esdata:/usr/share/elasticsearch/data
    environment:
      - discovery.type=single-node
      - xpack.ml.enabled=false
      - xpack.security.enabled=false
      - thread_pool.search.queue_size=5000
      - logger.org.elasticsearch.discovery="ERROR"
      - "ES_JAVA_OPTS=-Xms${ELASTIC_MEMORY_SIZE} -Xmx${ELASTIC_MEMORY_SIZE}"
    restart: always
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    healthcheck:
      test: curl -s http://elasticsearch:9200 >/dev/null || exit 1
      interval: 30s
      timeout: 10s
      retries: 50
  minio:
    image: minio/minio:RELEASE.2024-05-28T17-19-04Z
    volumes:
      - s3data:/data
    ports:
      - "9000:9000"
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}      
    command: server /data
    restart: always
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 10s
      timeout: 5s
      retries: 3
  rabbitmq:
    image: rabbitmq:4.1-management
    environment:
      - RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER}
      - RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS}
      - RABBITMQ_NODENAME=rabbit01@localhost
    volumes:
      - type: bind
        source: ./rabbitmq.conf
        target: /etc/rabbitmq/rabbitmq.conf
      - amqpdata:/var/lib/rabbitmq
    restart: always
    healthcheck:
      test: rabbitmq-diagnostics -q ping
      interval: 30s
      timeout: 30s
      retries: 3
  opencti:
    image: opencti/platform:6.6.14
    environment:
      - NODE_OPTIONS=--max-old-space-size=8096
      - APP__PORT=8080
      - APP__BASE_URL=${OPENCTI_BASE_URL}
      - APP__ADMIN__EMAIL=${OPENCTI_ADMIN_EMAIL}
      - APP__ADMIN__PASSWORD=${OPENCTI_ADMIN_PASSWORD}
      - APP__ADMIN__TOKEN=${OPENCTI_ADMIN_TOKEN}
      - APP__APP_LOGS__LOGS_LEVEL=error
      - REDIS__HOSTNAME=redis
      - REDIS__PORT=6379
      - ELASTICSEARCH__URL=http://elasticsearch:9200
      - MINIO__ENDPOINT=minio
      - MINIO__PORT=9000
      - MINIO__USE_SSL=false
      - MINIO__ACCESS_KEY=${MINIO_ROOT_USER}
      - MINIO__SECRET_KEY=${MINIO_ROOT_PASSWORD}
      - RABBITMQ__HOSTNAME=rabbitmq
      - RABBITMQ__PORT=5672
      - RABBITMQ__MANAGEMENT_SSL=false
      - RABBITMQ__USERNAME=${RABBITMQ_DEFAULT_USER}
      - RABBITMQ__PASSWORD=${RABBITMQ_DEFAULT_PASS}
      - PROVIDERS__LOCAL__STRATEGY=LocalStrategy
      - APP__HEALTH_ACCESS_KEY=${OPENCTI_HEALTHCHECK_ACCESS_KEY}
    ports:
      - "8080:8080"
    depends_on:
      redis:
        condition: service_healthy
      elasticsearch:
        condition: service_healthy
      minio:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
    restart: always
    healthcheck:
      test:  ["CMD", "wget", "-qO-", "http://opencti:8080/health?health_access_key=${OPENCTI_HEALTHCHECK_ACCESS_KEY}"]
      interval: 10s
      timeout: 5s
      retries: 20
  worker:
    image: opencti/worker:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - WORKER_LOG_LEVEL=info
    depends_on:
      opencti:
        condition: service_healthy
    deploy:
      mode: replicated
      replicas: 3
    restart: always
  connector-export-file-stix:
    image: opencti/connector-export-file-stix:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=${CONNECTOR_EXPORT_FILE_STIX_ID}
      - CONNECTOR_TYPE=INTERNAL_EXPORT_FILE
      - CONNECTOR_NAME=ExportFileStix2
      - CONNECTOR_SCOPE=application/json
      - CONNECTOR_LOG_LEVEL=info
    restart: always
    depends_on:
      opencti:
        condition: service_healthy
  connector-export-file-csv:
    image: opencti/connector-export-file-csv:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=${CONNECTOR_EXPORT_FILE_CSV_ID}
      - CONNECTOR_TYPE=INTERNAL_EXPORT_FILE
      - CONNECTOR_NAME=ExportFileCsv
      - CONNECTOR_SCOPE=text/csv
      - CONNECTOR_LOG_LEVEL=info
    restart: always
    depends_on:
      opencti:
        condition: service_healthy
  connector-export-file-txt:
    image: opencti/connector-export-file-txt:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=${CONNECTOR_EXPORT_FILE_TXT_ID}
      - CONNECTOR_TYPE=INTERNAL_EXPORT_FILE
      - CONNECTOR_NAME=ExportFileTxt
      - CONNECTOR_SCOPE=text/plain
      - CONNECTOR_LOG_LEVEL=info
    restart: always
    depends_on:
      opencti:
        condition: service_healthy
  connector-import-file-stix:
    image: opencti/connector-import-file-stix:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=${CONNECTOR_IMPORT_FILE_STIX_ID}
      - CONNECTOR_TYPE=INTERNAL_IMPORT_FILE
      - CONNECTOR_NAME=ImportFileStix
      - CONNECTOR_VALIDATE_BEFORE_IMPORT=true
      - CONNECTOR_SCOPE=application/json,text/xml
      - CONNECTOR_AUTO=true
      - CONNECTOR_LOG_LEVEL=info
    restart: always
    depends_on:
      opencti:
        condition: service_healthy
  connector-import-document:
    image: opencti/connector-import-document:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=${CONNECTOR_IMPORT_DOCUMENT_ID}
      - CONNECTOR_TYPE=INTERNAL_IMPORT_FILE
      - CONNECTOR_NAME=ImportDocument
      - CONNECTOR_VALIDATE_BEFORE_IMPORT=true
      - CONNECTOR_SCOPE=application/pdf,text/plain,text/html
      - CONNECTOR_AUTO=true
      - CONNECTOR_CONFIDENCE_LEVEL=15
      - CONNECTOR_LOG_LEVEL=info
      - IMPORT_DOCUMENT_CREATE_INDICATOR=true
    restart: always
    depends_on:
      opencti:
        condition: service_healthy
  connector-analysis:
    image: opencti/connector-import-document:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=${CONNECTOR_ANALYSIS_ID}
      - CONNECTOR_TYPE=INTERNAL_ANALYSIS
      - CONNECTOR_NAME=ImportDocumentAnalysis
      - CONNECTOR_SCOPE=application/pdf,text/plain,text/html
      - CONNECTOR_AUTO=true
      - CONNECTOR_CONFIDENCE_LEVEL=15
      - CONNECTOR_LOG_LEVEL=info
    restart: always
    depends_on:
      opencti:
        condition: service_healthy
  connector-alienvault:
    image: opencti/connector-alienvault:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=9a301ac0-5e8e-42e3-9435-77dc8b2f6783
      - CONNECTOR_TYPE=EXTERNAL_IMPORT
      - CONNECTOR_NAME=AlienVault
      - CONNECTOR_SCOPE=alienvault
      - CONNECTOR_CONFIDENCE_LEVEL=15
      - CONNECTOR_LOG_LEVEL=error
      - ALIENVAULT_API_KEY= # PASTE YOUR ALIENVAULT OTX API KEY HERE
      - ALIENVAULT_PULSE_START_TIMESTAMP=2025-03-01T00:00:00Z
      - ALIENVAULT_INTERVAL_SEC=1800
    restart: always
    depends_on:
      opencti:
        condition: service_healthy
  connector-mitre:
    image: opencti/connector-mitre:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=3df35d69-f528-4744-afb4-d96a065b957d
      - "CONNECTOR_NAME=MITRE Datasets"
      - CONNECTOR_SCOPE=tool,report,malware,identity,campaign,intrusion-set,attack-pattern,course-of-action,x-mitre-data-source,x-mitre-data-component,x-mitre-matrix,x-mitre-tactic,x-mitre-collection
      - CONNECTOR_LOG_LEVEL=error
      - MITRE_INTERVAL_SEC=1800 
    restart: always
    depends_on:
      opencti:
        condition: service_healthy
  connector-opencti:
    image: opencti/connector-opencti:6.6.14
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=a93fe3d0-a430-4f94-9c2f-85e123207f3b
      - CONNECTOR_NAME=OpenCTI Datasets
      - CONNECTOR_SCOPE=marking-definition,identity,location
      - CONNECTOR_UPDATE_EXISTING_DATA=true
      - CONNECTOR_LOG_LEVEL=error
      - CONFIG_INTERVAL_SEC=1800
    restart: always
    depends_on:
      opencti:
        condition: service_healthy

volumes:
  esdata:
  s3data:
  redisdata:
  amqpdata:
Action Required:
  1. Replace all Your... values with strong, unique passwords.
  2. Go to uuidgenerator.net and generate a new UUIDv4 for every single CONNECTOR_*_ID and the OPENCTI_ADMIN_TOKEN.
  3. Fill in your AlienVault OTX API key if you have one.

Launch the Stack!

Now for the magic. Open a terminal in your project directory and run the command:

docker compose up -d

My Troubleshooting Journey: Errors & Fixes

Setting this up wasn’t a straight path. Here are the errors I faced, why they happened, and how you can avoid them. This is the most valuable part of the guide!

Error 1: MinIO “Invalid Credentials”

  • The Symptom: The initial startup failed, and logs for the opencti container showed errors connecting to MinIO. The MinIO logs themselves mentioned Invalid credentials and a hint about key length.
  • The Cause: My initial MINIO_ROOT_PASSWORD in the .env file was root, which is only 4 characters long. MinIO enforces a minimum password length of 8 characters for security.
  • The Fix: Changed the password to a stronger one with 8+ characters. Lesson: Read the logs! They often tell you exactly what’s wrong.

Error 2: MinIO Container “Unhealthy”

  • The Symptom: After fixing the password, the stack still wouldn’t start. Running docker ps -a showed the minio container was in an Exited or Unhealthy state.
  • The Cause: The first failed startup attempt had already created a persistent Docker volume for MinIO with the bad configuration. When I restarted, the container tried to use this old, corrupt data and failed.
  • The Fix: A complete reset. The docker compose down command doesn’t remove volumes by default. The fix was to run docker compose down -v. The -v flag is crucial as it removes the volumes, forcing the services to start from a clean slate.

Error 3: Connector “API not reachable”

  • The Symptom: My AlienVault connector container was in a crash loop. The logs showed ValueError: OpenCTI API is not reachable.
  • The Cause: I had incorrectly set the OPENCTI_URL in the connector’s environment to http://localhost:8080. Inside a Docker container, localhost refers to the container itself, not the host machine or other containers.
  • The Fix: Corrected the URL to use Docker’s internal DNS: OPENCTI_URL=http://opencti:8080. The service name (opencti) acts as a valid hostname within the Docker network.

Error 4: Connector “Unknown type ThreatActorsFiltering”

  • The Symptom: After fixing the URL, the connector still crashed. This time the log showed a specific GraphQL error: Unknown type "ThreatActorsFiltering".
  • The Cause: This was a classic version mismatch. I was trying to use an old connector image (5.6.2) with a much newer version of the OpenCTI platform (6.x). The API had changed, and the old connector was speaking a language the new platform didn’t understand.
  • The Fix: I updated the image tag in my docker-compose.yml to match the platform version (opencti/connector-alienvault:6.6.14). Lesson: Always ensure your OpenCTI platform and connector versions are compatible.

Conclusion

Deploying OpenCTI is a journey, but it’s an incredibly rewarding one. While the setup can have its hurdles, every error is a valuable lesson in Docker networking, data persistence, and configuration management. By following this guide and understanding the potential pitfalls, you can build a stable and powerful threat intelligence platform ready to be populated with data.

Happy threat hunting!

To get in touch with me or for general discussion please visit ZeroDayMindset Discussion

This post is licensed under CC BY 4.0 by the author.