Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
from django.db import migrations
from django.db.models.fields.related_descriptors import (
ForwardManyToOneDescriptor,
ForwardOneToOneDescriptor,
ManyToManyDescriptor,
)

plugin = {
"python_module": {
"health_check_schedule": None,
"update_schedule": None,
"module": "ailtyposquatting.AilTypoSquatting",
"base_path": "api_app.analyzers_manager.observable_analyzers",
},
"name": "AILTypoSquatting",
"description": "[AILTypoSquatting](https://github.com/typosquatter/ail-typo-squatting) is a Python library to generate list of potential typo squatting domains with domain name permutation engine to feed AIL and other systems.",
"disabled": False,
"soft_time_limit": 60,
"routing_key": "default",
"health_check_status": True,
"type": "observable",
"docker_based": False,
"maximum_tlp": "RED",
"observable_supported": ["domain"],
"supported_filetypes": [],
"run_hash": False,
"run_hash_type": "",
"not_supported_filetypes": [],
"model": "analyzers_manager.AnalyzerConfig",
}

params = [
{
"python_module": {
"module": "ailtyposquatting.AilTypoSquatting",
"base_path": "api_app.analyzers_manager.observable_analyzers",
},
"name": "dns_resolving",
"type": "bool",
"description": "dns_resolving for AilTypoSquatting; only works for TLP CLEAR",
"is_secret": False,
"required": False,
},
]
values = [
{
"parameter": {
"python_module": {
"module": "ailtyposquatting.AilTypoSquatting",
"base_path": "api_app.analyzers_manager.observable_analyzers",
},
"name": "dns_resolving",
"type": "bool",
"description": "dns_resolving for AilTypoSquatting; only works for TLP CLEAR",
"is_secret": False,
"required": False,
},
"analyzer_config": "AILTypoSquatting",
"connector_config": None,
"visualizer_config": None,
"ingestor_config": None,
"pivot_config": None,
"for_organization": False,
"value": False,
"updated_at": "2024-05-26T00:10:15.236358Z",
"owner": None,
},
]


def _get_real_obj(Model, field, value):
def _get_obj(Model, other_model, value):
if isinstance(value, dict):
real_vals = {}
for key, real_val in value.items():
real_vals[key] = _get_real_obj(other_model, key, real_val)
value = other_model.objects.get_or_create(**real_vals)[0]
# it is just the primary key serialized
else:
if isinstance(value, int):
if Model.__name__ == "PluginConfig":
value = other_model.objects.get(name=plugin["name"])
else:
value = other_model.objects.get(pk=value)
else:
value = other_model.objects.get(name=value)
return value

if (
type(getattr(Model, field))
in [ForwardManyToOneDescriptor, ForwardOneToOneDescriptor]
and value
):
other_model = getattr(Model, field).get_queryset().model
value = _get_obj(Model, other_model, value)
elif type(getattr(Model, field)) in [ManyToManyDescriptor] and value:
other_model = getattr(Model, field).rel.model
value = [_get_obj(Model, other_model, val) for val in value]
return value


def _create_object(Model, data):
mtm, no_mtm = {}, {}
for field, value in data.items():
value = _get_real_obj(Model, field, value)
if type(getattr(Model, field)) is ManyToManyDescriptor:
mtm[field] = value
else:
no_mtm[field] = value
try:
o = Model.objects.get(**no_mtm)
except Model.DoesNotExist:
o = Model(**no_mtm)
o.full_clean()
o.save()
for field, value in mtm.items():
attribute = getattr(o, field)
if value is not None:
attribute.set(value)
return False
return True


def migrate(apps, schema_editor):
Parameter = apps.get_model("api_app", "Parameter")
PluginConfig = apps.get_model("api_app", "PluginConfig")
python_path = plugin.pop("model")
Model = apps.get_model(*python_path.split("."))
if not Model.objects.filter(name=plugin["name"]).exists():
exists = _create_object(Model, plugin)
if not exists:
for param in params:
_create_object(Parameter, param)
for value in values:
_create_object(PluginConfig, value)


def reverse_migrate(apps, schema_editor):
python_path = plugin.pop("model")
Model = apps.get_model(*python_path.split("."))
Model.objects.get(name=plugin["name"]).delete()


class Migration(migrations.Migration):
atomic = False
dependencies = [
("api_app", "0062_alter_parameter_python_module"),
("analyzers_manager", "0091_analyzer_config_vulners"),
]

operations = [migrations.RunPython(migrate, reverse_migrate)]
41 changes: 41 additions & 0 deletions api_app/analyzers_manager/observable_analyzers/ailtyposquatting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import logging
import math

from ail_typo_squatting import runAll
from ail_typo_squatting.dns_local.resolving import dnsResolving

from api_app.analyzers_manager import classes

logger = logging.getLogger(__name__)


class AilTypoSquatting(classes.ObservableAnalyzer):
"""
wrapper for https://github.com/typosquatter/ail-typo-squatting
"""

dns_resolving: bool = False

def update(self) -> bool:
pass

def run(self):
response = {}
logger.info(
f"""running AilTypoSquatting on {self.observable_name}
with tlp {self._job.tlp}
and dns resolving {self.dns_resolving}"""
)
resultList = []
response["algorithms"] = runAll(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please show the results from this? cause "formatoutput"="yara" I don't think it is the right option

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Member

@mlodic mlodic May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I mean it does not make sense to me cause I should have expected "Yara" code output. But ok, nevermind, the important thing is that it works. Maybe Yara would be used in the file written to the disk be but we are not generating it.
So, it could make sense to set it as None too to avoid confusion to the next reader

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

says : known format: None. Will use text format instead
committing with text only.

domain=self.observable_name,
limit=math.inf,
formatoutput="yara",
pathOutput=None,
)
if self._job.tlp == "CLEAR" and self.dns_resolving:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the enum for the compare? self._job.TLP.CLEAR.value

response["dnsResolving"] = dnsResolving(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also results from this plz

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
give nothing here...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a problem cause I would have expected a populated answer. For instance, xx.com does exist.

The output should be like this: https://github.com/typosquatter/ail-typo-squatting?tab=readme-ov-file#dns-output Can you check the library closely pls?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Figuring out the problem took quite some time. seems to work fine now. It just takes awfully lot of time.
attching a part of the output


{'k-com.xn--ogbpf8fl': {'NotExist': True}, 'k-com.xn--otu796d': {'NotExist': True}, 'k-com.xn--p1acf': {'NotExist': True}, 'k-com.xn--p1ai': {'NotExist': True}, 'k-com.xn--pgbs0dh': {'NotExist': True}, 'k-com.xn--pssy2u': {'NotExist': True}, 'k-com.xn--q7ce6a': {'NotExist': True}, 'k-com.xn--q9jyb4c': {'NotExist': True}, 'k-com.xn--qcka1pmc': {'NotExist': True}, 'k-com.xn--qxa6a': {'NotExist': True}, 'k-com.xn--qxam': {'NotExist': True}, 'k-com.xn--rhqv96g': {'NotExist': True}, 'k-com.xn--rovu88b': {'NotExist': True}, 'k-com.xn--rvc1e0am3e': {'NotExist': True}, 'k-com.xn--s9brj9c': {'NotExist': True}, 'k-com.xn--ses554g': {'NotExist': True}, 'k-com.xn--t60b56a': {'NotExist': True}, 'k-com.xn--tckwe': {'NotExist': True}, 'k-com.xn--tiq49xqyj': {'NotExist': True}, 'k-com.xn--unup4y': {'NotExist': True}, 'k-com.xn--vermgensberater-ctb': {'NotExist': True}, 'k-com.xn--vermgensberatung-pwb': {'NotExist': True}, 'k-com.xn--vhquv': {'NotExist': True}, 'k-com.xn--vuq861b': {'NotExist': True}, 'k-com.xn--w4r85el8fhu5dnra': {'NotExist': True}, 'k-com.xn--w4rs40l': {'NotExist': True}, 'k-com.xn--wgbh1c': {'NotExist': True}, 'k-com.xn--wgbl6a': {'NotExist': True}, 'k-com.xn--xhq521b': {'NotExist': True}, 'k-com.xn--xkc2al3hye2a': {'NotExist': True}, 'k-com.xn--xkc2dl3a5ee0h': {'NotExist': True}, 'k-com.xn--y9a3aq': {'NotExist': True}, 'k-com.xn--yfro4i67o': {'NotExist': True}, 'k-com.xn--ygbi2ammx': {'NotExist': True}, 'k-com.xn--zfr164b': {'NotExist': True}, 'k-com.xxx': {'NotExist': True}, 'k-com.xyz': {'NotExist': True}, 'k-com.yachts': {'NotExist': True}, 'k-com.yahoo': {'NotExist': True}, 'k-com.yamaxun': {'NotExist': True}, 'k-com.yandex': {'NotExist': True}, 'k-com.ye': {'NotExist': True}, 'k-com.yodobashi': {'NotExist': True}, 'k-com.yoga': {'NotExist': True}, 'k-com.yokohama': {'NotExist': True}, 'k-com.you': {'NotExist': True}, 'k-com.youtube': {'NotExist': True}, 'k-com.yt': {'NotExist': True}, 'k-com.yun': {'NotExist': True}, 'k-com.za': {'NotExist': True}, 'k-com.zappos': {'NotExist': True}, 'k-com.zara': {'NotExist': True}, 'k-com.zero': {'NotExist': True}, 'k-com.zip': {'NotExist': True}, 'k-com.zm': {'NotExist': True}, 'k-com.zone': {'NotExist': True}, 'k-com.zuerich': {'NotExist': True}, 'k-com.zw': {'NotExist': True}, 'k-com': {'NotExist': True}}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool man! :)

resultList=resultList, domain=self.observable_name, pathOutput=None
)

return response
1 change: 1 addition & 0 deletions docs/source/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ The following is the list of the available analyzers you can run out-of-the-box.
* `HudsonRock`: [Hudson Rock](https://cavalier.hudsonrock.com/docs) provides its clients the ability to query a database of over 27,541,128 computers which were compromised through global info-stealer campaigns performed by threat actors.
* `CyCat`: [CyCat](https://cycat.org/) or the CYbersecurity Resource CATalogue aims at mapping and documenting, in a single formalism and catalogue available cybersecurity tools, rules, playbooks, processes and controls.
* `Vulners`: [Vulners](vulners.com) is the most complete and the only fully correlated security intelligence database, which goes through constant updates and links 200+ data sources in a unified machine-readable format. It contains 8 mln+ entries, including CVEs, advisories, exploits, and IoCs — everything you need to stay abreast on the latest security threats.
* `AILTypoSquatting`:[AILTypoSquatting](https://github.com/typosquatter/ail-typo-squatting) is a Python library to generate list of potential typo squatting domains with domain name permutation engine to feed AIL and other systems.

##### Generic analyzers (email, phone number, etc.; anything really)

Expand Down
2 changes: 1 addition & 1 deletion requirements/project-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ querycontacts==2.0.0
blint==2.1.5
hfinger==0.2.2
permhash==0.1.4

ail_typo_squatting==2.7.4
# this is required because XLMMacroDeobfuscator does not pin the following packages
pyxlsb2==0.0.8
xlrd2==1.3.4
Expand Down