mcpField
B
building-ioc-enrichment-pipeline-with-opencti

building-ioc-enrichment-pipeline-with-opencti

github.com/mukul975安全35.2k152 次安装8 周前更新
代码仓库

关于 building-ioc-enrichment-pipeline-with-opencti

用于构建 OpenCTI 指标丰富流水线的技能,可借助外部威胁情报为新导入的指标补充信息。内容介绍连接器、STIX 2.1 上下文及置信度评分,面向安全分析人员。

主要功能

  1. 01自动丰富指标
  2. 02接入外部情报连接器
  3. 03添加 STIX 2.1 上下文与关系
  4. 04更新置信度评分
  5. 05自定义 Python 连接器模式

适用场景

  1. 01为新指标补充上下文
  2. 02将指标关联至威胁
  3. 03为分析人员排列指标优先级
  4. 04构建自定义 OpenCTI 丰富流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill building-ioc-enrichment-pipeline-with-opencti

安装后在 Claude Code 中输入/building-ioc-enrichment-pipeline-with-opencti确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE
assets/
template.md
references/
api-reference.md
standards.md
workflows.md
scripts/
agent.py
process.py

详细介绍

使用 OpenCTI 构建 IOC 富化流水线

概述

OpenCTI 是一个用于管理网络威胁情报知识的开源平台,以 STIX 2.1 作为其原生数据模型。本技能涵盖如何使用 OpenCTI 的连接器生态系统构建自动化 IOC 富化流水线,从 VirusTotal、Shodan、AbuseIPDB、GreyNoise 及其他来源为指标补充上下文信息。该流水线会自动富化新接入的指标,将其与已知的威胁行为者和攻击活动进行关联,并为其评分以供分析师确定优先级。

何时使用

  • 在您的环境中部署或配置使用 OpenCTI 构建 IOC 富化流水线的能力时
  • 建立与合规要求一致的安全控制时
  • 为该领域构建或改进安全架构时
  • 执行需要此实现的安全评估时

前置条件

  • 用于部署 OpenCTI 的 Docker 和 Docker Compose
  • Python 3.9+ 及 pycti 库
  • 富化服务的 API 密钥:VirusTotal、Shodan、AbuseIPDB、GreyNoise
  • 了解 STIX 2.1 数据模型及其关系
  • 用于 OpenCTI 后端的 ElasticSearch 或 OpenSearch
  • 用于连接器消息传递的 RabbitMQ 或 Redis

核心概念

OpenCTI 架构

OpenCTI 使用 GraphQL API 前端,后端由 ElasticSearch 提供存储,并使用 Redis/RabbitMQ 进行连接器通信。数据以带有关系的 STIX 2.1 对象原生存储。连接器分为以下几类:外部导入(情报源接入)、内部导入(文件解析)、内部富化(添加上下文)以及流(实时导出)。

富化连接器模型

内部富化连接器会在创建新可观测对象时自动触发,也可由分析师手动触发。每个连接器接收 STIX 对象,查询外部服务,并返回 STIX 2.1 包,以附加的上下文、标签和关系来增强原始可观测对象。

置信度评分

OpenCTI 对指标使用 0-100 的置信度评分。富化连接器可以基于外部验证更新置信度评分:VirusTotal 检测比率、Shodan 暴露数据、AbuseIPDB 报告数量以及 GreyNoise 分类结果。

工作流

步骤 1:使用 Docker Compose 部署 OpenCTI
# docker-compose.yml (key services)
version: '3'
services:
  opencti:
    image: opencti/platform:6.4.4
    environment:
      - APP__PORT=8080
      - [email protected]
      - APP__ADMIN__PASSWORD=ChangeMeNow
      - APP__ADMIN__TOKEN=your-admin-token-uuid
      - ELASTICSEARCH__URL=http://elasticsearch:9200
      - MINIO__ENDPOINT=minio
      - RABBITMQ__HOSTNAME=rabbitmq
    ports:
      - "8080:8080"
    depends_on:
      - elasticsearch
      - minio
      - rabbitmq
      - redis

  connector-virustotal:
    image: opencti/connector-virustotal:6.4.4
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=your-admin-token-uuid
      - CONNECTOR_ID=connector-virustotal-id
      - CONNECTOR_NAME=VirusTotal
      - CONNECTOR_SCOPE=StixFile,Artifact,IPv4-Addr,Domain-Name,Url
      - CONNECTOR_AUTO=true
      - VIRUSTOTAL_TOKEN=your-vt-api-key
      - VIRUSTOTAL_MAX_TLP=TLP:AMBER

  connector-shodan:
    image: opencti/connector-shodan:6.4.4
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=your-admin-token-uuid
      - CONNECTOR_ID=connector-shodan-id
      - CONNECTOR_NAME=Shodan
      - CONNECTOR_SCOPE=IPv4-Addr
      - CONNECTOR_AUTO=true
      - SHODAN_TOKEN=your-shodan-api-key
      - SHODAN_MAX_TLP=TLP:AMBER

  connector-abuseipdb:
    image: opencti/connector-abuseipdb:6.4.4
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=your-admin-token-uuid
      - CONNECTOR_ID=connector-abuseipdb-id
      - CONNECTOR_NAME=AbuseIPDB
      - CONNECTOR_SCOPE=IPv4-Addr
      - CONNECTOR_AUTO=true
      - ABUSEIPDB_API_KEY=your-abuseipdb-key
步骤 2:构建自定义富化连接器
import os
from pycti import OpenCTIConnectorHelper, get_config_variable
from stix2 import (
    Bundle, Indicator, Note, Relationship,
    IPv4Address, DomainName
)
import requests


class CustomEnrichmentConnector:
    def __init__(self):
        config = {
            "opencti": {
                "url": os.environ.get("OPENCTI_URL"),
                "token": os.environ.get("OPENCTI_TOKEN"),
            },
            "connector": {
                "id": os.environ.get("CONNECTOR_ID"),
                "name": "CustomEnrichment",
                "scope": "IPv4-Addr,Domain-Name,Url",
                "auto": True,
                "type": "INTERNAL_ENRICHMENT",
            },
        }
        self.helper = OpenCTIConnectorHelper(config)
        self.helper.listen(self._process_message)

    def _process_message(self, data):
        entity_id = data["entity_id"]
        stix_object = self.helper.api.stix_cyber_observable.read(id=entity_id)

        if not stix_object:
            return "Observable not found"

        observable_type = stix_object["entity_type"]
        observable_value = stix_object.get("value", "")

        enrichment_results = []

        if observable_type == "IPv4-Addr":
            enrichment_results = self._enrich_ip(observable_value, entity_id)
        elif observable_type == "Domain-Name":
            enrichment_results = self._enrich_domain(observable_value, entity_id)

        if enrichment_results:
            bundle = Bundle(objects=enrichment_results, allow_custom=True)
            self.helper.send_stix2_bundle(bundle.serialize())

        return "Enrichment completed"

    def _enrich_ip(self, ip_address, entity_id):
        """Enrich IP address with GreyNoise, AbuseIPDB context."""
        objects = []

        # GreyNoise Community API
        try:
            gn_response = requests.get(
                f"https://api.greynoise.io/v3/community/{ip_address}",
                headers={"key": os.environ.get("GREYNOISE_API_KEY")},
                timeout=30,
            )
            if gn_response.status_code == 200:
                gn_data = gn_response.json()
                classification = gn_data.get("classification", "unknown")
                noise = gn_data.get("noise", False)
                riot = gn_data.get("riot", False)

                note_content = (
                    f"## GreyNoise Enrichment\n"
                    f"- Classification: {classification}\n"
                    f"- Internet Noise: {noise}\n"
                    f"- RIOT (Benign Service): {riot}\n"
                    f"- Name: {gn_data.get('name', 'N/A')}\n"
                    f"- Last Seen: {gn_data.get('last_seen', 'N/A')}"
                )

                note = Note(
                    content=note_content,
                    object_refs=[entity_id],
                    abstract=f"GreyNoise: {classification}",
                    allow_custom=True,
                )
                objects.append(note)

                # Add labels based on classification
                if classification == "malicious":
                    self.helper.api.stix_cyber_observable.add_label(
                        id=entity_id, label_name="greynoise:malicious"
                    )
                elif riot:
                    self.helper.api.stix_cyber_observable.add_label(
                        id=entity_id, label_name="greynoise:benign-service"
                    )

        except Exception as e:
            self.helper.log_error(f"GreyNoise enrichment failed: {e}")

        return objects

    def _enrich_domain(self, domain, entity_id):
        """Enrich domain with WHOIS and DNS context."""
        objects = []

        try:
            # Use SecurityTrails API for domain enrichment
            st_response = requests.get(
                f"https://api.securitytrails.com/v1/domain/{domain}",
                headers={"APIKEY": os.environ.get("SECURITYTRAILS_API_KEY")},
                timeout=30,
            )
            if st_response.status_code == 200:
                st_data = st_response.json()
                current_dns = st_data.get("current_dns", {})

                a_records = [
                    r.get("ip") for r in current_dns.get("a", {}).get("values", [])
                ]

                note_content = (
                    f"## SecurityTrails Enrichment\n"
                    f"- A Records: {', '.join(a_records)}\n"
                    f"- Alexa Rank: {st_data.get('alexa_rank', 'N/A')}\n"
                    f"- Hostname: {st_data.get('hostname', 'N/A')}"
                )

                note = Note(
                    content=note_content,
                    object_refs=[entity_id],
                    abstract=f"SecurityTrails: {domain}",
                    allow_custom=True,
                )
                objects.append(note)

        except Exception as e:
            self.helper.log_error(f"SecurityTrails enrichment failed: {e}")

        return objects


if __name__ == "__main__":
    connector = CustomEnrichmentConnector()

评论

支持 Markdown