Bonjour à tous, aujourd’hui on va se pencher sur la plateforme OpenCTI avec docker et portainer, comme d’habitude. OpenCTI c’est une plateforme de Cyber-Threat Intelligence qui a vocation a accueillir et gérer votre base de données d’indicateurs de compromission de sources externes ou internes et à servir de point de pivot avec l’ensemble de votre écosystème : cortex et the hive, AbuseIPDB, abuse.ch, les CVE du NIST, Splunk, et j’en passe… L’intérêt ? c’est que c’est un plateforme industrialisable avec des capacité de reporting intégré, une API toute prête, et un paquet de plugins communautaire déjà fait.
Rappel des articles de la série « Docker et Portainer » :
- Docker et Portainer part 1 – Les conteneurs pour les nuls ;
- Docker et Portainer part 2 – Stack vsftpd mono-image ;
- Docker et Portainer part 3 – Docker Guacamole via une Stack multi-images ;
- Docker et Portainer part 4 – OpenVPN, Network et Splunk ;
- Docker et Portainer part 5 – Customiser un conteneur PHP-FPM ;
- Docker et Portainer part 6 – NextCloud avec Docker, Déployer un « Cloud » personnel ;
- Docker et Portainer part 7 – Mettre à jour Portainer ;
- Docker et Portainer part 8 – Déployer un jitsi meet avec docker ;
- Docker et Portainer part 9 – Monitoring des performances docker avec Splunk ;
- Docker et Portainer part 10 – The Hive & Cortex et installation Docker… ;
- Docker et Portainer part 11 – OpenCTI
Bref, vous vous pouvez voir OpenCTI comme un confrère de MISP, ThreatQuotient et Anomali.
Son petit inconvénient c’est que le projet, bien que très actif, est encore jeune (Première release en Juin 2019). Son avantage c’est que le projet est très actif comme je l’ai dit et Open Source (donc pas trop couteux 🙂 ), que c’est un peu plus « propre » qu’une installation de MISP par exemple. Et en cerise sur le gâteau l’ANSSI contribue au projet !
Installation d’OpenCTI avec Docker Compose
Alors la documentation du projet est très bien faite, mais elle ne colle pas avec mon setup derrière un reverse proxy et portainter que vous connaissez. Du coup j’ai refait mon propre docker-compose.yml. Au préalable vous devez préparer un fichier .env
qu’il faudra injecter dans portainer en même temps que votre stack docker.
Le fichier .env
à préparer :
OPENCTI_ADMIN_EMAIL=etienne@geekeries.org
OPENCTI_ADMIN_PASSWORD=
OPENCTI_ADMIN_TOKEN=
OPENCTI_BASE_URL=http://cti.geekeries.org
MINIO_ROOT_USER=opencti
MINIO_ROOT_PASSWORD=
RABBITMQ_DEFAULT_USER=opencti
RABBITMQ_DEFAULT_PASS=
CONNECTOR_EXPORT_FILE_STIX_ID=dd817c8b-abae-460a-9ebc-97b1551e70e6
CONNECTOR_EXPORT_FILE_CSV_ID=7ba187fb-fde8-4063-92b5-c3da34060dd7
CONNECTOR_EXPORT_FILE_TXT_ID=ca715d9c-bd64-4351-91db-33a8d728a58b
CONNECTOR_IMPORT_FILE_STIX_ID=72327164-0b35-482b-b5d6-a5a3f76b845f
CONNECTOR_IMPORT_DOCUMENT_ID=c3970f8a-ce4b-4497-a381-20b7256f56f0
SMTP_HOSTNAME=smtp.geekeries.org
Quelques modification système recommandée sur le serveur dans doc d’installation. Pas testé sans, je vous laisse juger de la nécessité.
sysctl -w vm.max_map_count=1048575 echo "vm.max_map_count=1048575" >> /etc/sysctl.conf
A partir de là, vous n’avez plus qu’à embarquer la stack suivante
version: '3' services: redis: image: redis:7.0.0 restart: always volumes: - redisdata:/data networks: Proxy_net: ipv4_address: 172.20.1.1 elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.4 volumes: - esdata:/usr/share/elasticsearch/data environment: # Comment out the line below for single-node - discovery.type=single-node # Uncomment line below below for a cluster of multiple nodes # - cluster.name=docker-cluster - xpack.ml.enabled=false - "ES_JAVA_OPTS=-Xms${ELASTIC_MEMORY_SIZE} -Xmx${ELASTIC_MEMORY_SIZE}" restart: always ulimits: memlock: soft: -1 hard: -1 nofile: soft: 65536 hard: 65536 networks: Proxy_net: ipv4_address: 172.20.1.2 minio: image: minio/minio:RELEASE.2022-05-19T18-20-59Z volumes: - s3data:/data #ports: #- "9000:9000" expose: - 9000 environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} command: server /data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 20s retries: 3 restart: always networks: Proxy_net: ipv4_address: 172.20.1.3 rabbitmq: image: rabbitmq:3.10-management environment: - RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER} - RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS} volumes: - amqpdata:/var/lib/rabbitmq restart: always networks: Proxy_net: ipv4_address: 172.20.1.4 opencti: image: opencti/platform:5.3.7 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__PORT_MANAGEMENT=15672 - RABBITMQ__MANAGEMENT_SSL=false - RABBITMQ__USERNAME=${RABBITMQ_DEFAULT_USER} - RABBITMQ__PASSWORD=${RABBITMQ_DEFAULT_PASS} - SMTP__HOSTNAME=${SMTP_HOSTNAME} - SMTP__PORT=25 - PROVIDERS__LOCAL__STRATEGY=LocalStrategy #ports: #- "8080:8080" expose: - 8080 depends_on: - redis - elasticsearch - minio - rabbitmq restart: always networks: Proxy_net: ipv4_address: 172.20.1.5 worker: image: opencti/worker:5.3.7 environment: - OPENCTI_URL=http://opencti:8080 - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN} - WORKER_LOG_LEVEL=info depends_on: - opencti deploy: mode: replicated replicas: 3 restart: always networks: Proxy_net: connector-export-file-stix: image: opencti/connector-export-file-stix:5.3.7 environment: - OPENCTI_URL=http://opencti:8080 - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN} - CONNECTOR_ID=${CONNECTOR_EXPORT_FILE_STIX_ID} # Valid UUIDv4 - CONNECTOR_TYPE=INTERNAL_EXPORT_FILE - CONNECTOR_NAME=ExportFileStix2 - CONNECTOR_SCOPE=application/json - CONNECTOR_CONFIDENCE_LEVEL=15 # From 0 (Unknown) to 100 (Fully trusted) - CONNECTOR_LOG_LEVEL=info restart: always depends_on: - opencti networks: Proxy_net: ipv4_address: 172.20.1.7 connector-export-file-csv: image: opencti/connector-export-file-csv:5.3.7 environment: - OPENCTI_URL=http://opencti:8080 - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN} - CONNECTOR_ID=${CONNECTOR_EXPORT_FILE_CSV_ID} # Valid UUIDv4 - CONNECTOR_TYPE=INTERNAL_EXPORT_FILE - CONNECTOR_NAME=ExportFileCsv - CONNECTOR_SCOPE=text/csv - CONNECTOR_CONFIDENCE_LEVEL=15 # From 0 (Unknown) to 100 (Fully trusted) - CONNECTOR_LOG_LEVEL=info restart: always depends_on: - opencti networks: Proxy_net: ipv4_address: 172.20.1.8 connector-export-file-txt: image: opencti/connector-export-file-txt:5.3.7 environment: - OPENCTI_URL=http://opencti:8080 - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN} - CONNECTOR_ID=${CONNECTOR_EXPORT_FILE_TXT_ID} # Valid UUIDv4 - CONNECTOR_TYPE=INTERNAL_EXPORT_FILE - CONNECTOR_NAME=ExportFileTxt - CONNECTOR_SCOPE=text/plain - CONNECTOR_CONFIDENCE_LEVEL=15 # From 0 (Unknown) to 100 (Fully trusted) - CONNECTOR_LOG_LEVEL=info restart: always depends_on: - opencti networks: Proxy_net: ipv4_address: 172.20.1.9 connector-import-file-stix: image: opencti/connector-import-file-stix:5.3.7 environment: - OPENCTI_URL=http://opencti:8080 - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN} - CONNECTOR_ID=${CONNECTOR_IMPORT_FILE_STIX_ID} # Valid UUIDv4 - CONNECTOR_TYPE=INTERNAL_IMPORT_FILE - CONNECTOR_NAME=ImportFileStix - CONNECTOR_VALIDATE_BEFORE_IMPORT=true # Validate any bundle before import - CONNECTOR_SCOPE=application/json,text/xml - CONNECTOR_AUTO=true # Enable/disable auto-import of file - CONNECTOR_CONFIDENCE_LEVEL=15 # From 0 (Unknown) to 100 (Fully trusted) - CONNECTOR_LOG_LEVEL=info restart: always depends_on: - opencti networks: Proxy_net: ipv4_address: 172.20.1.10 connector-import-document: image: opencti/connector-import-document:5.3.7 environment: - OPENCTI_URL=http://opencti:8080 - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN} - CONNECTOR_ID=${CONNECTOR_IMPORT_DOCUMENT_ID} # Valid UUIDv4 - CONNECTOR_TYPE=INTERNAL_IMPORT_FILE - CONNECTOR_NAME=ImportDocument - CONNECTOR_VALIDATE_BEFORE_IMPORT=true # Validate any bundle before import - CONNECTOR_SCOPE=application/pdf,text/plain,text/html - CONNECTOR_AUTO=true # Enable/disable auto-import of file - CONNECTOR_ONLY_CONTEXTUAL=false # Only extract data related to an entity (a report, a threat actor, etc.) - CONNECTOR_CONFIDENCE_LEVEL=15 # From 0 (Unknown) to 100 (Fully trusted) - CONNECTOR_LOG_LEVEL=info - IMPORT_DOCUMENT_CREATE_INDICATOR=true restart: always depends_on: - opencti networks: Proxy_net: ipv4_address: 172.20.1.11 networks: Proxy_net: external: true volumes: esdata: s3data: redisdata: amqpdata:
Et go : « Deploy Stack » dans Portainer (pensez bien à importer le .env
dans la console portainer).
A partir de là vous devriez pouvoir vous loguer sur la plateforme (après configuration de votre reverse proxy NPM) avec les accès spécifiés dans le .env.
Configurer une input de données
Bon c’est bien beau tout ça mais si j’ai pas de données dedans, elle ne me sert pas à grand chose cette plateforme, non ? Et bien oui, et c’est la la 1ère chose à faire : créer des « connecteurs »pour envoyer de la données dans la plateforme. Cela se fait tout naturellement en ajoutant des images dockers dans votre stack. Voici deux exemple pour les CVE et les URL d’urlhaus.
connector-urlhaus: image: opencti/connector-urlhaus:5.3.7 environment: - OPENCTI_URL=http://opencti:8080 - OPENCTI_TOKEN= - HTTPS_PROXY= - CONNECTOR_ID=Abusech_urlhaus - CONNECTOR_TYPE=EXTERNAL_IMPORT - "CONNECTOR_NAME=Abuse.ch URLhaus" - CONNECTOR_SCOPE=urlhaus - CONNECTOR_CONFIDENCE_LEVEL=40 # From 0 (Unknown) to 100 (Fully trusted) - CONNECTOR_UPDATE_EXISTING_DATA=false - CONNECTOR_LOG_LEVEL=info - URLHAUS_CSV_URL=https://urlhaus.abuse.ch/downloads/csv_recent/ - URLHAUS_IMPORT_OFFLINE=true - URLHAUS_CREATE_INDICATORS=true - URLHAUS_INTERVAL=2 # In days, must be strictly greater than 1 restart: always networks: Proxy_net: ipv4_address: 172.20.1.12 connector-cve: image: opencti/connector-cve:5.3.7 environment: - OPENCTI_URL=http://opencti:8080 - OPENCTI_TOKEN= - HTTPS_PROXY= - CONNECTOR_ID=nvdcve - CONNECTOR_TYPE=EXTERNAL_IMPORT - CONNECTOR_NAME=Common Vulnerabilities and Exposures - CONNECTOR_SCOPE=identity,vulnerability - CONNECTOR_CONFIDENCE_LEVEL=75 # From 0 (Unknown) to 100 (Fully trusted) - CONNECTOR_UPDATE_EXISTING_DATA=false - CONNECTOR_RUN_AND_TERMINATE=false - CONNECTOR_LOG_LEVEL=info - CVE_IMPORT_HISTORY=true # Import history at the first run (after only recent), reset the connector state if you want to re-import - CVE_NVD_DATA_FEED=https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-recent.json.gz - CVE_HISTORY_START_DATE=2002 - CVE_HISTORY_DATA_FEED=https://nvd.nist.gov/feeds/json/cve/1.1/ - CVE_INTERVAL=2 # In days, must be strictly greater than 1 restart: always networks: Proxy_net: ipv4_address: 172.20.1.13
Notez la variable HTTPS_PROXY, ca peut être un peu tricky à faire fonctionner derrière des proxy explicites pour le coup mais on finit par y arriver :-). Anecdote, à mon ancien taf, ils galéraient un peu à rentrer une base du NIST dans un MISP. Avec OpenCTI ca m’a pris moins d’une heure là, téléchargement inclus, (coucou André si tu me lis… 😉 ). Pour finir, une liste des connecteurs est consultable facilement ici.
SI vous commencer je vous conseille fortement de commencer avec :
- ABUSE.CH URLHAUS
- COMMON VULNERABILITIES AND EXPOSURES
Puis ajouter :
- APT & CYBERCRIMINALS CAMPAIGN COLLECTION
- CYBERCRIME-TRACKER
- URLHAUS RECENT PAYLOADS
Ensuite vous pourrez passer au augmentation qui nécessite une clé d’API :
- ipinfo
- abuseipdb
- shodan
- virustotal
Et déjà vous serez pas mal !
Créer des Streams de sortie.
Une fois vos données dans la plateforme vous pouvez simplement créé des streams en allant dans Données, Partage de données, et Créer un Stream « flux CSV » comme ci dessous.
Ces Streams vont vous permettre de définir des filtres pour exporter ou exposer facilement des sous-ensemble de votre CTI.
Créer des connecteurs pour OpenCTI
Bon et si y’a pas l’input de données qui vous intéresse ? et bien il suffit de créer la vôtre, je vous laisse regarder le code d’un connecteur mais c’est assez simple au final, la plupart des connecteurs d’import sont au final un script python d’une grosse centaine de ligne.
Selon moi, la partie la plus complexe restant le format d’import STIX qui, s’il a le mérite d’être complet et clair, n’est probablement pas le plus simple à utiliser. En particulier, si vous venez avec des besoins assez basique (genre injecter une grosse liste d’IP que jugée suspectes mais avec peu de contexte) le format STIX me semble plus embêtant qu’autre chose. Par contre pour décrire des relations complexes entre vos IoC (tels Hash sont reliés à tel C&C, qui est associé à tel malware qui fait partie de l’APT machin-chouette), la c’est nickel.
J’ai quelques projet de connecteurs en tête. Peut-être je vous ferai un post la dessus plus tard !
Conclusion
Voilà, c’est tout pour aujourd’hui et OpenCTI, de mon point de vue c’est un super outil, bien plus agréable à utiliser que MISP et avec du potentiel à développer. Je vous ajoute un peu de lecture sur la CTI avant de vous laisser :
- Débuter avec la Cyber Threat Intelligence à destination des blueteams
- Une introduction à la plateforme libre de renseignements MISP
- DFIR et CTI, une complémentarité idéale
That’s all Folks : Geekez bien !