mirror of https://github.com/MISP/misp-galaxy
269 lines
9.8 KiB
Python
269 lines
9.8 KiB
Python
"""
|
|
|
|
Author: Jose Luis Sanchez Martinez
|
|
Twitter: @Joseliyo_Jstnk
|
|
date: 2022/11/18
|
|
Modified: 2023/01/03
|
|
GitHub: https://github.com/jstnk9/MISP
|
|
Description: This script can create MISP Galaxies from Sigma Rules. It can be done setting the path
|
|
where you have stored your sigma rules in the system.
|
|
Examples: python sigma-to-galaxy -p "C:\lab\sigma\rules\" -r
|
|
MISP Galaxy: https://github.com/MISP/misp-galaxy
|
|
|
|
"""
|
|
|
|
import os, json, yaml, argparse, uuid, configparser, time
|
|
|
|
unique_uuid = '9cf7cd2e-d5f1-48c4-9909-7896ba1c96b2'
|
|
|
|
|
|
def main(args):
|
|
uuidGalaxy = create_galaxy_json()
|
|
galaxyCluster = create_cluster(uuidGalaxy=unique_uuid)
|
|
valuesData = create_cluster_value(args.inputPath, args.recursive, galaxyCluster)
|
|
galaxyCluster["values"].extend(valuesData)
|
|
galaxyCluster = createRelations(galaxyCluster)
|
|
create_cluster_json(galaxyCluster)
|
|
check_duplicates(galaxyCluster)
|
|
|
|
|
|
def createRelations(galaxyCluster):
|
|
"""
|
|
:param galaxyCluster: Content of the cluster with all the values related to the Sigma Rules
|
|
|
|
:return galaxyCluster: Content of the cluster adding the relation between sigma rule and MITRE technique
|
|
"""
|
|
for obj in galaxyCluster["values"]:
|
|
for attack in obj["meta"]["tags"]:
|
|
if attack.startswith("attack.t"):
|
|
with open(
|
|
config["MISP"]["cluster_path"]
|
|
+ config["MISP"]["mitre_attack_cluster"],
|
|
"r",
|
|
) as mitreCluster:
|
|
data = json.load(mitreCluster)
|
|
for technique in data["values"]:
|
|
if (
|
|
technique["meta"]["external_id"]
|
|
== attack.split(".", 1)[1].upper()
|
|
):
|
|
if obj.get("related"):
|
|
obj["related"].append(
|
|
{
|
|
"dest-uuid": "%s" % (technique["uuid"]),
|
|
"tags": [
|
|
"estimative-language:likelihood-probability=\"almost-certain\""
|
|
],
|
|
"type": "related-to",
|
|
}
|
|
)
|
|
else:
|
|
obj["related"] = []
|
|
obj["related"].append(
|
|
{
|
|
"dest-uuid": "%s" % (technique["uuid"]),
|
|
"tags": [
|
|
"estimative-language:likelihood-probability=\"almost-certain\""
|
|
],
|
|
"type": "related-to",
|
|
}
|
|
)
|
|
|
|
return galaxyCluster
|
|
|
|
|
|
def check_duplicates(galaxy):
|
|
"""
|
|
:param galaxy: Content of the cluster with all the values
|
|
|
|
:return res:
|
|
"""
|
|
galaxiesObj = {}
|
|
for val in galaxy["values"]:
|
|
obj = {}
|
|
if galaxiesObj.get(val["value"]):
|
|
galaxiesObj[val["value"]].append(val["uuid"])
|
|
else:
|
|
galaxiesObj[val["value"]] = []
|
|
galaxiesObj[val["value"]].append(val["uuid"])
|
|
|
|
for k, v in galaxiesObj.items():
|
|
if len(v) > 1:
|
|
print("[*] Title duplicated: %s " % (k))
|
|
for ids in v:
|
|
print(" %s" % (ids))
|
|
|
|
|
|
def create_cluster_json(galaxyCluster):
|
|
"""
|
|
:param galaxyCluster: Content of the cluster with all the values related to the Sigma Rules
|
|
|
|
This function finally creates the sigma-cluster.json file with all the information.
|
|
"""
|
|
with open("sigma-cluster.json", "w") as f:
|
|
json.dump(galaxyCluster, f)
|
|
|
|
|
|
def parseYaml(inputPath, yamlFile):
|
|
"""
|
|
:param inputPath: Path where is stored the Sigma Rule to parse.
|
|
:param yamlFile: Content of the Sigma Rule.
|
|
|
|
This function can convert a Sigma Rule to JSON (dict)
|
|
|
|
:return jsonData: Sigma rule converted to dict.
|
|
"""
|
|
fullPath = os.path.join(inputPath, yamlFile)
|
|
with open(fullPath, encoding='utf-8') as f:
|
|
jsonData = yaml.load(f, Loader=yaml.FullLoader)
|
|
return jsonData
|
|
|
|
|
|
def create_cluster(uuidGalaxy=unique_uuid):
|
|
"""
|
|
:param uuidGalaxy: Is the uuid4 generated for the galaxy JSON file previously.
|
|
|
|
This function creates the JSON file of the path /app/files/misp-galaxy/clusters without values.
|
|
|
|
:return cluster: Dict with the basic information needed for the JSON file.
|
|
"""
|
|
version = int(time.strftime("%Y%m%d"))
|
|
cluster = {
|
|
"authors": ["@Joseliyo_Jstnk"],
|
|
"category": "rules",
|
|
"description": "MISP galaxy cluster based on Sigma Rules.",
|
|
"name": "Sigma-Rules",
|
|
"source": "https://github.com/jstnk9/MISP/tree/main/misp-galaxy/sigma",
|
|
"type": "sigma-rules",
|
|
"uuid": uuidGalaxy,
|
|
"values": [],
|
|
"version": version
|
|
}
|
|
|
|
return cluster
|
|
|
|
|
|
def create_cluster_value(pathsigma, recursive, galaxyCluster):
|
|
"""
|
|
:param pathsigma: Is the path established with the -p parameter
|
|
:param recurisve: If true, it can recursively navigate through every subfolder of :pathsigma:
|
|
:param galaxyCluster: Dictionary with the information needed for the cluster JSON file
|
|
|
|
This function makes a loop in every subfolder to identify Sigma Rules and after that..
|
|
1. It parse the YAML file to dict
|
|
2. Once it's a dict, it call the function to parse the dict and start creating the
|
|
values of the cluster
|
|
|
|
IMPORTANT: Sigma rules must ends with .yml and not .yaml
|
|
|
|
:return valuesData: Array with every Sigma Rule parsed into a dict.
|
|
"""
|
|
valuesData = []
|
|
if recursive == True:
|
|
for dirpath, dirs, files in os.walk(pathsigma):
|
|
if os.name == 'nt':
|
|
path = dirpath.split('/')[0]
|
|
else:
|
|
path = dirpath
|
|
|
|
for f in files:
|
|
if f.endswith(".yml"):
|
|
jsonData = parseYaml(path, f)
|
|
valuesData.append(
|
|
parse_sigma_to_cluster(jsonData, f, path.split("rules")[1])
|
|
)
|
|
|
|
return valuesData
|
|
|
|
|
|
def parse_sigma_to_cluster(jsonData, sigmaFile, sigmaPath):
|
|
"""
|
|
:param jsonData: Is the Sigma Rule parsed to dict.
|
|
:param sigmaFile: Is the Sigma Rule filename.
|
|
:param sigmaPath: Is the path where are stored the Sigma Rules.
|
|
|
|
This function parse the dict of the Sigma Rule to fill all the fields needed for the MISP Galaxy.
|
|
|
|
:return valueData: Dict with all the fields filled ready to be added in the cluster JSON file.
|
|
|
|
"""
|
|
valueData = {}
|
|
valueData["description"] = jsonData.get("description", "No established description")
|
|
valueData["uuid"] = jsonData.get("id", "No established id")
|
|
valueData["value"] = jsonData.get("title", "No established title")
|
|
valueData["meta"] = {}
|
|
valueData["meta"]["refs"] = []
|
|
if jsonData.get("references"):
|
|
for rf in jsonData.get("references"):
|
|
valueData["meta"]["refs"].append(rf)
|
|
valueData["meta"]["refs"] = [
|
|
*set(valueData["meta"]["refs"])
|
|
] # Removing duplicated references
|
|
valueData["meta"]["tags"] = jsonData.get("tags", "No established tags")
|
|
valueData["meta"]["creation_date"] = jsonData.get("date", "No established date")
|
|
valueData["meta"]["filename"] = sigmaFile
|
|
valueData["meta"]["author"] = jsonData.get("author", "No established author")
|
|
valueData["meta"]["level"] = jsonData.get("level", "No established level")
|
|
valueData["meta"]["falsepositive"] = jsonData.get(
|
|
"falsepositives", "No established falsepositives"
|
|
)
|
|
valueData["meta"]["refs"].append(
|
|
"https://github.com/SigmaHQ/sigma/tree/master/rules%s/%s"
|
|
% (sigmaPath.replace("\\", "/"), sigmaFile)
|
|
) # this value only works if you set the path like it was cloned from github
|
|
valueData["meta"]["logsource.category"] = jsonData.get("logsource").get(
|
|
"category", "No established category"
|
|
)
|
|
valueData["meta"]["logsource.product"] = jsonData.get("logsource").get(
|
|
"product", "No established product"
|
|
)
|
|
return valueData
|
|
|
|
|
|
def create_galaxy_json():
|
|
"""
|
|
This method creates first the galaxy JSON stored in the path /app/files/misp-galaxy/galaxies
|
|
The information of this JSON is basic.
|
|
|
|
:return uuidGalaxy: Return the uuid needed for the cluster JSON File which is created after this.
|
|
"""
|
|
uuidGalaxy = unique_uuid
|
|
galaxy = {
|
|
"description": "Sigma Rules are used to detect suspicious behaviors related to threat actors, malware and tools",
|
|
"icon": "link",
|
|
"name": "Sigma-Rules",
|
|
"namespace": "misp",
|
|
"type": "sigma-rules",
|
|
"uuid": uuidGalaxy,
|
|
"version": 1,
|
|
}
|
|
with open("sigma-rules.json", "w") as f:
|
|
json.dump(galaxy, f)
|
|
|
|
return uuidGalaxy
|
|
|
|
|
|
if __name__ == '__main__':
|
|
config = configparser.ConfigParser()
|
|
config.read("config.ini")
|
|
parser = argparse.ArgumentParser(
|
|
description="This script can convert your sigma rules in MISP galaxies, generating both files needed for cluster and galaxies. If you need more information about how to import it, please, go to https://github.com/jstnk9/MISP/tree/main/misp-galaxy/sigma"
|
|
)
|
|
parser.add_argument(
|
|
"-p",
|
|
"--path",
|
|
dest="inputPath",
|
|
required=True,
|
|
default="None",
|
|
help="Path with your sigma rules.",
|
|
)
|
|
parser.add_argument(
|
|
"-r",
|
|
"--recursive",
|
|
dest="recursive",
|
|
action="store_true",
|
|
help="If you have subfolders on the initial path and you want to convert all of them, use -r to do it recursive.",
|
|
)
|
|
args = parser.parse_args()
|
|
main(args)
|