1. Introduction
Nous voici dans le deuxième épisode de cette série d’articles axée sur la CTI intégrée à QRadar. Nous avons vu précédemment que nous utilisions OpenCTI qui est une solution très complète ouverte au grand public.
Dans la suite de cet article vous allez voir toutes les étapes pour comprendre techniquement comment va être faite la partie orientée OpenCTI du script. Vous allez apprendre non seulement à interagir avec OpenCTI pour y récupérer des informations mais également à scripter le tout pour être le plus exhaustif et précis possible dans ce que vous souhaitez.
Vous verrez que nous allons nous focaliser sur les observables “IPv4” mais il y a bien d’autres observables sur la plateforme que vous serez en mesure de récupérer en utilisant le même procédé que celui que nous allons voir juste après.
2. Découverte du GraphQL
En nous penchant sur la documentation d’OpenCTI, on tombe assez rapidement sur le GraphQL et son playground qui sont accessibles pour pouvoir récupérer les informations d’OpenCTI via des requêtes web spécifiques.
Sans expliquer de A à Z ce qu’est le GraphQL, il faut que vous reteniez que c’est un langage pour effectuer des requêtes du côté du client. Ces requêtes côté client vont accéder à des informations côté serveur, qui selon les paramètres, l’environnement et plein d’autres variables, vont retourner au client ces dites informations.
a. Bases des requêtes
Comme dit juste avant, OpenCTI vous propose un playground qui va vous permettre de manipuler ces requêtes très facilement pour trouver la bonne sans avoir à mettre en place d’énormes moyens.
Pour y accéder, c’est très simple, il vous suffit d’aller à cette URL : https://demo.opencti.io/graphql. Avant de pouvoir effectuer votre première requête, il vous faut votre token qui va permettre au serveur de savoir que c’est bien vous qui effectuez chaque requête. Pour ce faire, il faut vous connecter à OpenCTI et aller dans la section “Profile“, vous y trouverez tout en bas le “Required headers” qu’il vous faut garder de côté. Vous noterez également qu’il y a un bouton pour ouvrir le Playground en dessous.
Avant de pouvoir effectuer votre première requête, il va falloir compléter les headers comme ci-dessous avec votre token personnel récupéré juste avant.
Une fois que c’est fait dans l’encadré de la requête (notée 1 dans la capture ci-dessous), vous copiez-collez ce code :
query{
about {
version
dependencies {
name
version
}
}
}
Puis vous cliquez sur le bouton “Play” au centre de l’écran et vous allez obtenir un résultat similaire à ce dernier.
Si vous connaissez un peu le GraphQL (ou si vous êtes doués pour la langue de Shakespeare 😁) vous comprenez que cette requête permet d’avoir des informations quant à la plateforme de démo d’OpenCTI.
Si vous êtes curieux de découvrir par vous-même, vous avez la documentation de tout ce qu’il est possible de faire sur la droite de l’écran du Playground en cliquant sur “DOCS”. Vous pouvez également découvrir les différentes options de requêtes avec l’autocomplétion via le raccourci CTRL+TAB.
b. Récupération des observables
Passons maintenant au vif du sujet, à savoir la récupération des observables. Je vais vous passer sous silence les nombreux essais infructueux que j’ai pu faire mais plutôt vous montrer le cheminement de pensée que j’ai eu en m’appuyant sur la documentation pour obtenir ce que je souhaitais.
Voici le résumé en une capture :
Si on décortique tout cela :
- J’ouvre l’onglet de la documentation GraphQL directement depuis mon Playground
- Je tape le mot clé que je souhaite, ici “observable” puisque l’on a vu que dans OpenCTI c’était ainsi qu’étaient appelées les informations
- Dans la liste des résultats, je vois une fonction “QuerystixCyberObservables” qui semble coller à mes besoins, je clique dessus pour avoir plus de détails
- Je vois que dans les paramètres de cette fonction, j’ai un paramètre de recherche, chouette nous allons donc pouvoir tester tout cela avec une petite recherche d’observables manuellement
- Ensuite, si on schématise (très) vulgairement, je vois que la fonction me retourne un objet de type “StixCyberObservableConnection” qui contient des node “StyxCyberObservable” qui contiennent eux les informations de l’observable en question (id, nom…)
Avant de nous lancer tête baissée dans notre recherche, faisons le point sur ce que nous souhaitons récupérer : des IPv4. C’est donc pour cela que j’ai surligné en jaune dans la capture plus haut le paramètre “entity_type”. En effet, si nous réussissons à récupérer ce type, nous pourrons ensuite filtrer avec ce dernier et donc récupérer toutes les IPv4 stockées dans OpenCTI. Pour ce faire, nous allons procéder ainsi :
- Rechercher un observable de type IPv4 dans OpenCTI
- Effectuer une recherche via le Playground de cette IPv4 en affichant le paramètre “entity_type”
- Filtrer avec le résultat
Pour le premier point, je vous propose d’aller dans la GUI d’OpenCTI dans la section des observables et de prendre une IPv4 au hasard. Copiez-collez cette IPv4 et remplacez la ligne en surbrillance dans le code avec votre IPv4.
query{
stixCyberObservables(
search: "255.255.255.255"
)
{
edges{
node{
entity_type
observable_value
}
}
}
}
Lancez votre recherche puis vous allez obtenir un résultat similaire à celui-ci :
{
"data": {
"stixCyberObservables": {
"edges": [
{
"node": {
"entity_type": "IPv4-Addr",
"observable_value": "X.X.X.X"
}
}
]
}
}
}
Vous voyez donc que le type de ces observables est “IPv4-Addr”. Nous allons donc maintenant modifier notre requête comme ceci pour intégrer ce filtre.
query{
stixCyberObservables(
filters: [
{
key: entity_type
values: "IPv4-Addr"
}
]
)
{
edges{
node{
entity_type
observable_value
}
}
}
}
En exécutant la requête on obtient ce type de résultat :
Ce qui correspond parfaitement à ce que nous souhaitions, à savoir des IPv4 seulement. Nous pouvons donc passer à la partie formatage.
c. Formatage des observables
Nous venons de finaliser la partie récupération des adresses IPv4 qui sont dans OpenCTI, maintenant nous allons voir rapidement les modifications que nous allons apporter à notre requête pour que nous récupérions en réponse ce qui nous convient.
Dans un premier temps nous allons voir qu’il est possible de filtrer sur la date et ainsi de pouvoir récupérer les observables au jour le jour en exécutant le script tous les jours par exemple. Pour cela nous allons filtrer sur le paramètre “created_at” qui, comme son nom l’indique, correspond à la date de création de l’observable.
query{
stixCyberObservables(
filters: [
{
key: entity_type
values: "IPv4-Addr"
},
{
key: created_at
values: "2023-02-03"
}
]
)
{
edges{
node{
entity_type
observable_value
created_at
}
}
}
}
Vous pouvez également changer votre façon de récupérer les informations en vous basant sur la date de mise à jour des observables qui est filtrable via le paramètre “update_at”. Ensuite, vous voyez que dans l’exemple plus haut je n’ai pas mis d’opérateur pour mon filtre, par défaut le filtre va donc s’appliquer quand la clé est égale à la valeur. Vous pouvez néanmoins utiliser des opérateurs tel que “lt” pour “less than” ou “gt” pour “greater than” et ainsi avoir des intervalles plus précis.
Pour conclure sur la partie formatage, nous allons demander via notre requête à avoir plus d’informations dans la réponse. En effet, dans la présentation du projet, nous avions mentionné un score OpenCTI, ce score qui va nous permettre de maintenir à jour notre référentiel avec les IPv4 les plus à jour et les plus cohérents pour la détection. Pour ce faire, il suffit de rajouter le paramètre “x_opencti_score” dans les paramètres que l’on souhaite avoir pour les nodes comme ceci :
query{
stixCyberObservables(
filters: [
{
key: entity_type
values: "IPv4-Addr"
},
{
key: created_at
values: "2023-02-03"
operator: "gt"
}
]
)
{
edges{
node{
entity_type
observable_value
x_opencti_score
created_at
}
}
}
}
Ainsi si l’on résume la requête cela signifie que l’on veut : “Tous les observables de type IPv4 qui ont été créés après le 03 février 2023. Pour les observables récupérés, je souhaite avoir son type, sa valeur, son score OpenCTI ainsi que sa date de création”.
3. Requêtes Python
Maintenant que nous avons notre requête prête, nous allons voir comment intégrer cela dans un script. J’ai choisi d’utiliser le Python ici, et comme très souvent dans ce langage, nous allons utiliser le module “requests” pour effectuer nos requêtes web.
Ainsi si vous n’avez pas le module “requests” dans votre environnement Python, il vous faut l’installer. Pour ce faire plusieurs moyens s’offrent à vous mais le plus simple reste tout de même de faire :
$ pip install requests
a. Récupération des données
Pour récupérer les données, d’un point de vue web, nous avons besoin d’effectuer une requête “POST” auprès d’OpenCTI. Pour faire cela il faut utiliser le module comme ci-suit :
import requests
OpenCTI_request = requests.post(OpenCTI_request_URL, json=OpenCTI_request_query, headers=OpenCTI_request_headers)
Avec les variables suivantes :
- “OpenCTI_request_URL” : L’URL de la console d’OpenCTI que vous contactez, cela peut être la console de démo (https://demo.opencti.io/graphql) ou votre propre console si vous avez une instance personnelle
- “OpenCTI_request_query” : La requête que nous avons construite dans la partie précédente
- “OpenCTI_request_headers” : Le header qui va contenir le token notamment
Si on concatène le tout on obtient ce que vous retrouverez en Annexe 1.
Une fois la dernière ligne exécutée, si tout est bon vous pouvez exécuter la ligne suivante et valider que le retour est “200”, cela signifiera que la requête s’est bien passé et que la réponse du serveur OpenCTI est “OK”.
OpenCTI_request.status_code
Si le code n’est pas “200”, vous pouvez débug en effectuant la commande suivante qui va vous afficher le retour fourni par le serveur OpentCTI.
OpenCTI_request.text
Nous avons maintenant notre requête fonctionnelle, passons à la suite des événements pour récupérer les informations comme il le faut.
b. Formatage des données
Dans la partie précédente nous avons utilisé la commande “OpenCTI_request.text” pour vérifier ce qu’il n’allait pas quand le code de retour n’était pas “200”, mais cette méthode retourne également la réponse du serveur quand tout fonctionne. Autrement dit, cela signifie que vous y trouverez au format brute toutes les informations dont nous avons besoin pour la suite.
Par exemple, voici ce que vous pourrez obtenir (j’ai volontairement obfusqué les adresses IP pour vous éviter les mauvaises surprises 🙂) :
>>> OpenCTI_request.text
'{"data":{"stixCyberObservables":{"edges":[{"node":{"entity_type":"IPv4-Addr","observable_value":"X.X.X.X","x_opencti_score":50,"created_at":"2023-02-07T14:21:07.063Z"}},{"node":{"entity_type":"IPv4-Addr","observable_value":"X.X.X.X","x_opencti_score":50,"created_at":"2023-02-07T14:21:15.687Z"}},{"node":{"entity_type":"IPv4-Addr","observable_value":"X.X.X.X","x_opencti_score":50,"created_at":"2023-02-06T17:20:35.040Z"}},{"node":{"entity_type":"IPv4-Addr","observable_value":"X.X.X.X","x_opencti_score":50,"created_at":"2023-02-06T14:20:25.879Z"}}]}}}\n'
Cette grosse chaîne de caractère étant très peu exploitable nous allons faire appel au module “json” pour magnifier et être en mesure de pouvoir utiliser tout cela.
import json
OpenCTI_request_json = json.loads(OpenCTI_request.text)
Une fois que vous avez transformé en objet JSON votre chaîne de caractères, vous allez être en mesure de lister toutes les adresses IPv4 accompagnées de leur score respectif comme ceci :
OpenCTI_request_elementsCount = len(OpenCTI_request_json["data"]["stixCyberObservables"]["edges"])
for node in range(OpenCTI_request_elementsCount):
print("""Le score de l'IP "{0}" est {1}/100 """.format(OpenCTI_request_json["data"]["stixCyberObservables"]["edges"][IP]["node"]["observable_value"], OpenCTI_request_json["data"]["stixCyberObservables"]["edges"][IP]["node"]["x_opencti_score"]))
Ensuite, libre à vous de récupérer les informations comme vous le souhaitez. En effet, les objets JSON sont très facilement manipulables et compréhensibles.
4. Conclusion
Nous voici déjà à la conclusion de ce deuxième chapitre concernant la récupération des informations dans OpenCTI. Comme vous avez pu le voir j’ai été à l’essentiel dans les explications mais OpenCTI vous propose un grand nombre de paramètres récupérables. Ainsi, vous pouvez désormais vous lancer dans d’autres types de récupération en vous basant sur les exemples que je vous ai présenté.
N’hésitez pas également à partager vos idées à ce sujet pour que nous puissions tous en profiter 😁
Rendez-vous maintenant à la prochaine étape : “La récupération et la manipulation des informations dans QRadar”.
5. Bibliographie
- Documentation GraphQL d’OpenCTI : GraphQL API
- Documentation Python au sujet de GraphQL : Using Python to Connect to a GraphQL API
- Playground OpenCTI avec la documentation GraphQL associée : Playground
6. Annexes
#1 : Requête OpenCTI avec variables
OpenCTI_request_URL = """https://demo.opencti.io/graphql"""
OpenCTI_request_headers = {"Authorization": "Bearer <TOKEN>"}
OpenCTI_request_query = {"query": """query {
stixCyberObservables (
filters: [
{
key: entity_type
values: "IPv4-Addr"
},
{
key: created_at
values: "2023-02-03"
operator: "gt"
}
]
)
{
edges{
node{
entity_type
observable_value
x_opencti_score
created_at
}
}
}
}"""}
OpenCTI_request = requests.post(OpenCTI_request_URL, json=OpenCTI_request_query, headers=OpenCTI_request_headers)
Il faut penser à remplacer la valeur “<TOKEN>” avec votre token OpenCTI.
Merci d’avoir suivi ce petit tuto, en espérant que cela vous ait été utile. N’hésitez pas à me communiquer vos ressentis, tips…etc via le formulaire ci-dessous.