milenium 0.5.7__tar.gz → 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {milenium-0.5.7 → milenium-1.0.2}/PKG-INFO +10 -3
- milenium-1.0.2/milenium/__init__.py +53 -0
- milenium-1.0.2/milenium/cli.py +180 -0
- milenium-1.0.2/milenium/modules/__init__.py +9 -0
- milenium-1.0.2/milenium/modules/abuseipdb_search.py +30 -0
- milenium-1.0.2/milenium/modules/aggregator.py +43 -0
- milenium-1.0.2/milenium/modules/censys_search.py +30 -0
- milenium-1.0.2/milenium/modules/holehe_search.py +17 -0
- milenium-1.0.2/milenium/modules/image_search.py +24 -0
- milenium-1.0.2/milenium/modules/ipinfo_search.py +28 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/logger.py +1 -1
- milenium-1.0.2/milenium/modules/maigret_search.py +17 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/neon_db.py +40 -30
- milenium-1.0.2/milenium/modules/sherlock_search.py +16 -0
- milenium-1.0.2/milenium/modules/shodan_search.py +31 -0
- milenium-1.0.2/milenium/modules/telegram_analysis.py +36 -0
- milenium-1.0.2/milenium/modules/urlscan_search.py +26 -0
- milenium-1.0.2/milenium/modules/virustotal_search.py +50 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/tui.py +155 -31
- {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/PKG-INFO +10 -3
- {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/SOURCES.txt +11 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/requires.txt +6 -0
- {milenium-0.5.7 → milenium-1.0.2}/pyproject.toml +10 -3
- milenium-0.5.7/milenium/__init__.py +0 -23
- milenium-0.5.7/milenium/cli.py +0 -328
- milenium-0.5.7/milenium/modules/__init__.py +0 -0
- milenium-0.5.7/milenium/modules/aggregator.py +0 -41
- {milenium-0.5.7 → milenium-1.0.2}/README.md +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/breach.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/breach_local.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/dehashed.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/dns_history.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/domain.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/dorker.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/email.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/email_validator.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/exif_extractor.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/github_search.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/gravatar.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/hibp.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/instagram_osint.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/ip.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/ip_geo_advanced.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/ip_reputation.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/leakcheck.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/password_pattern.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/permutator.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/pgp_lookup.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/phone.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/pwned_passwords.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/report.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/reverse_whois.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/shodan_lite.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/social.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/telegram_osint.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/tg_parser.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/twitter_osint.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/username.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/vk_osint.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/wayback.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/wifi_geolocation.py +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/dependency_links.txt +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/entry_points.txt +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/top_level.txt +0 -0
- {milenium-0.5.7 → milenium-1.0.2}/setup.cfg +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: milenium
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: OSINT
|
|
5
|
-
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: The most powerful OSINT aggregator
|
|
5
|
+
Author: VexxDev200
|
|
6
|
+
Requires-Python: >=3.10
|
|
6
7
|
Description-Content-Type: text/markdown
|
|
7
8
|
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: aiohttp
|
|
8
10
|
Requires-Dist: dnspython
|
|
9
11
|
Requires-Dist: phonenumbers
|
|
10
12
|
Requires-Dist: beautifulsoup4
|
|
@@ -12,6 +14,11 @@ Requires-Dist: rich
|
|
|
12
14
|
Requires-Dist: click
|
|
13
15
|
Requires-Dist: python-whois
|
|
14
16
|
Requires-Dist: psycopg2-binary
|
|
17
|
+
Requires-Dist: maigret
|
|
18
|
+
Requires-Dist: sherlock-project
|
|
19
|
+
Requires-Dist: holehe
|
|
20
|
+
Requires-Dist: telethon
|
|
21
|
+
Requires-Dist: PicImageSearch
|
|
15
22
|
|
|
16
23
|
# milenium
|
|
17
24
|
# exiftool
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _ensure_deps():
|
|
7
|
+
"""Автоустановка зависимостей при первом импорте."""
|
|
8
|
+
deps = {
|
|
9
|
+
"requests": "requests",
|
|
10
|
+
"aiohttp": "aiohttp",
|
|
11
|
+
"dns": "dnspython",
|
|
12
|
+
"phonenumbers": "phonenumbers",
|
|
13
|
+
"bs4": "beautifulsoup4",
|
|
14
|
+
"rich": "rich",
|
|
15
|
+
"click": "click",
|
|
16
|
+
"whois": "python-whois",
|
|
17
|
+
"psycopg2": "psycopg2-binary",
|
|
18
|
+
"telethon": "telethon",
|
|
19
|
+
"PicImageSearch": "PicImageSearch",
|
|
20
|
+
}
|
|
21
|
+
def _ensure_deps():
|
|
22
|
+
deps = {
|
|
23
|
+
"requests": "requests",
|
|
24
|
+
"aiohttp": "aiohttp",
|
|
25
|
+
"dns": "dnspython",
|
|
26
|
+
"phonenumbers": "phonenumbers",
|
|
27
|
+
"bs4": "beautifulsoup4",
|
|
28
|
+
"rich": "rich",
|
|
29
|
+
"click": "click",
|
|
30
|
+
"whois": "python-whois",
|
|
31
|
+
"psycopg2": "psycopg2-binary",
|
|
32
|
+
"telethon": "telethon",
|
|
33
|
+
"PicImageSearch": "PicImageSearch",
|
|
34
|
+
}
|
|
35
|
+
for mod, pkg in deps.items():
|
|
36
|
+
try:
|
|
37
|
+
importlib.import_module(mod)
|
|
38
|
+
except ImportError:
|
|
39
|
+
try:
|
|
40
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", pkg, "--quiet"])
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
# Прокидываем конфиг в окружение
|
|
44
|
+
try:
|
|
45
|
+
from milenium.modules import config
|
|
46
|
+
config.apply_to_env()
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_ensure_deps()
|
|
52
|
+
|
|
53
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import asyncio
|
|
5
|
+
import click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
from milenium.modules.logger import TeeLogger, progress
|
|
9
|
+
from milenium.modules import (
|
|
10
|
+
neon_db, aggregator,
|
|
11
|
+
shodan_search, censys_search, virustotal_search,
|
|
12
|
+
abuseipdb_search, ipinfo_search, urlscan_search,
|
|
13
|
+
maigret_search, sherlock_search, holehe_search,
|
|
14
|
+
telegram_analysis, image_search,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _init_logger():
|
|
21
|
+
logger = TeeLogger("milenium.log", mode="a")
|
|
22
|
+
sys.stdout = logger
|
|
23
|
+
return logger
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _save(command, target, module, data, target_type=None):
|
|
27
|
+
try:
|
|
28
|
+
neon_db.init()
|
|
29
|
+
neon_db.save(command, target, module, data, target_type=target_type)
|
|
30
|
+
progress("NeonDB: записано")
|
|
31
|
+
except Exception as e:
|
|
32
|
+
progress(f"NeonDB error: {e}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.group()
|
|
36
|
+
def main():
|
|
37
|
+
"""milenium — самый мощный OSINT агрегатор"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@main.command()
|
|
42
|
+
@click.argument("username")
|
|
43
|
+
def user(username):
|
|
44
|
+
"""Maigret + Sherlock + Holehe"""
|
|
45
|
+
logger = _init_logger()
|
|
46
|
+
t0 = time.time()
|
|
47
|
+
progress(f"старт: user {username}")
|
|
48
|
+
res = aggregator.run(username=username)
|
|
49
|
+
_save("user", username, "username", res, "username")
|
|
50
|
+
console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
|
|
51
|
+
progress(f"готово за {time.time() - t0:.1f}s")
|
|
52
|
+
logger.close()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@main.command()
|
|
56
|
+
@click.argument("email")
|
|
57
|
+
def email_cmd(email):
|
|
58
|
+
"""Holehe + VirusTotal domain"""
|
|
59
|
+
logger = _init_logger()
|
|
60
|
+
t0 = time.time()
|
|
61
|
+
progress(f"старт: email {email}")
|
|
62
|
+
res = aggregator.run(email=email)
|
|
63
|
+
_save("email", email, "email", res, "email")
|
|
64
|
+
console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
|
|
65
|
+
progress(f"готово за {time.time() - t0:.1f}s")
|
|
66
|
+
logger.close()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@main.command()
|
|
70
|
+
@click.argument("ip")
|
|
71
|
+
def ip_cmd(ip):
|
|
72
|
+
"""Shodan + Censys + VirusTotal + AbuseIPDB + ipinfo"""
|
|
73
|
+
logger = _init_logger()
|
|
74
|
+
t0 = time.time()
|
|
75
|
+
progress(f"старт: ip {ip}")
|
|
76
|
+
res = aggregator.run(ip=ip)
|
|
77
|
+
_save("ip", ip, "ip", res, "ip")
|
|
78
|
+
console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
|
|
79
|
+
progress(f"готово за {time.time() - t0:.1f}s")
|
|
80
|
+
logger.close()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@main.command()
|
|
84
|
+
@click.argument("url")
|
|
85
|
+
def url_cmd(url):
|
|
86
|
+
"""urlscan.io анализ"""
|
|
87
|
+
logger = _init_logger()
|
|
88
|
+
t0 = time.time()
|
|
89
|
+
progress(f"старт: url {url}")
|
|
90
|
+
res = aggregator.run(url=url)
|
|
91
|
+
_save("url", url, "url", res, "url")
|
|
92
|
+
console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
|
|
93
|
+
progress(f"готово за {time.time() - t0:.1f}s")
|
|
94
|
+
logger.close()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@main.command()
|
|
98
|
+
@click.argument("username")
|
|
99
|
+
def tg(username):
|
|
100
|
+
"""Telethon: инфа по TG-аккаунту"""
|
|
101
|
+
logger = _init_logger()
|
|
102
|
+
t0 = time.time()
|
|
103
|
+
progress(f"старт: telegram {username}")
|
|
104
|
+
res = asyncio.run(telegram_analysis.check_user(username))
|
|
105
|
+
_save("telegram", username, "telegram", res, "username")
|
|
106
|
+
for k, v in res.items():
|
|
107
|
+
console.print(f"[bold]{k}:[/bold] {v}")
|
|
108
|
+
progress(f"готово за {time.time() - t0:.1f}s")
|
|
109
|
+
logger.close()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@main.command()
|
|
113
|
+
@click.argument("image_path")
|
|
114
|
+
@click.option("--engine", default="yandex")
|
|
115
|
+
def img(image_path, engine):
|
|
116
|
+
"""PicImageSearch: обратный поиск по фото"""
|
|
117
|
+
logger = _init_logger()
|
|
118
|
+
t0 = time.time()
|
|
119
|
+
progress(f"старт: image search {engine}")
|
|
120
|
+
res = asyncio.run(image_search.search(image_path, engine))
|
|
121
|
+
_save("image", image_path, "image", res, "file")
|
|
122
|
+
console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
|
|
123
|
+
progress(f"готово за {time.time() - t0:.1f}s")
|
|
124
|
+
logger.close()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@main.command()
|
|
128
|
+
@click.option("--key", default=None, help="Какой ключ задать")
|
|
129
|
+
@click.option("--value", default=None, help="Значение")
|
|
130
|
+
def config_cmd(key, value):
|
|
131
|
+
"""Просмотр и настройка конфига"""
|
|
132
|
+
from milenium.modules import config
|
|
133
|
+
if key and value:
|
|
134
|
+
config.set_key(key, value)
|
|
135
|
+
console.print(f"[green]Сохранено:[/green] {key}")
|
|
136
|
+
return
|
|
137
|
+
cfg = config.all_keys()
|
|
138
|
+
console.print(f"[bold]Конфиг:[/bold] {config.config_path()}")
|
|
139
|
+
for k, v in cfg.items():
|
|
140
|
+
shown = v if not v else (v[:8] + "..." if len(v) > 12 else v)
|
|
141
|
+
console.print(f" [red]{k}[/red] = {shown or '[empty]'}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@main.command()
|
|
145
|
+
@click.option("--target", default=None)
|
|
146
|
+
@click.option("--stats", is_flag=True)
|
|
147
|
+
def db_neon(target, stats):
|
|
148
|
+
"""NeonDB статистика и поиск"""
|
|
149
|
+
logger = _init_logger()
|
|
150
|
+
try:
|
|
151
|
+
neon_db.init()
|
|
152
|
+
if stats:
|
|
153
|
+
s = neon_db.stats()
|
|
154
|
+
console.print(f"[bold]Всего:[/bold] {s['total']}")
|
|
155
|
+
console.print(f"[bold]Findings:[/bold] {s['findings']}")
|
|
156
|
+
console.print(f"[bold]Leaks:[/bold] {s['leaks']}")
|
|
157
|
+
else:
|
|
158
|
+
rows = neon_db.query(target=target, limit=50)
|
|
159
|
+
table = Table(title="NeonDB")
|
|
160
|
+
table.add_column("Время")
|
|
161
|
+
table.add_column("Команда")
|
|
162
|
+
table.add_column("Цель")
|
|
163
|
+
table.add_column("Модуль")
|
|
164
|
+
for r in rows:
|
|
165
|
+
table.add_row(str(r["ts"])[:19], r["command"], r["target"], r["module"])
|
|
166
|
+
console.print(table)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
console.print(f"[bold red]Ошибка Neon:[/bold red] {e}")
|
|
169
|
+
logger.close()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@main.command()
|
|
173
|
+
def tui():
|
|
174
|
+
"""TUI интерфейс"""
|
|
175
|
+
from milenium.tui import interactive
|
|
176
|
+
interactive()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
main()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"logger", "neon_db", "aggregator", "dorker",
|
|
3
|
+
"username", "social", "email", "phone", "ip", "domain", "breach", "report",
|
|
4
|
+
"pwned_passwords", "breach_local", "hibp", "dehashed", "leakcheck",
|
|
5
|
+
"shodan_lite", "shodan_search", "censys_search", "virustotal_search",
|
|
6
|
+
"abuseipdb_search", "ipinfo_search", "urlscan_search",
|
|
7
|
+
"telegram_osint", "telegram_analysis", "image_search",
|
|
8
|
+
"maigret_search", "sherlock_search", "holehe_search",
|
|
9
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import aiohttp
|
|
3
|
+
|
|
4
|
+
from milenium.modules import config
|
|
5
|
+
ABUSE_KEY = config.get("ABUSEIPDB_KEY", "")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def check(ip: str) -> dict:
|
|
9
|
+
"""AbuseIPDB: жалобы на IP."""
|
|
10
|
+
if not ABUSE_KEY:
|
|
11
|
+
return {"error": "ABUSEIPDB_KEY not set"}
|
|
12
|
+
url = "https://api.abuseipdb.com/api/v2/check"
|
|
13
|
+
params = {"ipAddress": ip, "maxAgeInDays": 90}
|
|
14
|
+
headers = {"Key": ABUSE_KEY, "Accept": "application/json"}
|
|
15
|
+
try:
|
|
16
|
+
async with aiohttp.ClientSession() as session:
|
|
17
|
+
async with session.get(url, params=params, headers=headers, timeout=15) as resp:
|
|
18
|
+
if resp.status != 200:
|
|
19
|
+
return {"error": f"HTTP {resp.status}"}
|
|
20
|
+
data = await resp.json()
|
|
21
|
+
d = data.get("data", {})
|
|
22
|
+
return {
|
|
23
|
+
"abuse_score": d.get("abuseConfidenceScore"),
|
|
24
|
+
"total_reports": d.get("totalReports"),
|
|
25
|
+
"country": d.get("countryCode"),
|
|
26
|
+
"isp": d.get("isp"),
|
|
27
|
+
"domain": d.get("domain"),
|
|
28
|
+
}
|
|
29
|
+
except Exception as e:
|
|
30
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from milenium.modules import (
|
|
3
|
+
shodan_search, censys_search, virustotal_search,
|
|
4
|
+
abuseipdb_search, ipinfo_search, urlscan_search,
|
|
5
|
+
maigret_search, sherlock_search, holehe_search,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def run_async(email=None, username=None, phone=None, ip=None, url=None):
|
|
10
|
+
tasks = {}
|
|
11
|
+
|
|
12
|
+
if ip:
|
|
13
|
+
tasks["shodan"] = shodan_search.check_ip(ip)
|
|
14
|
+
tasks["censys"] = censys_search.check_host(ip)
|
|
15
|
+
tasks["virustotal"] = virustotal_search.check_ip(ip)
|
|
16
|
+
tasks["abuseipdb"] = abuseipdb_search.check(ip)
|
|
17
|
+
tasks["ipinfo"] = ipinfo_search.check(ip)
|
|
18
|
+
|
|
19
|
+
if username:
|
|
20
|
+
tasks["maigret"] = maigret_search.check(username)
|
|
21
|
+
tasks["sherlock"] = sherlock_search.check(username)
|
|
22
|
+
|
|
23
|
+
if email:
|
|
24
|
+
tasks["holehe"] = holehe_search.check(email)
|
|
25
|
+
tasks["virustotal_domain"] = virustotal_search.check_domain(email.split("@")[-1])
|
|
26
|
+
|
|
27
|
+
if url:
|
|
28
|
+
tasks["urlscan"] = urlscan_search.check(url)
|
|
29
|
+
|
|
30
|
+
results = {}
|
|
31
|
+
if tasks:
|
|
32
|
+
keys = list(tasks.keys())
|
|
33
|
+
values = await asyncio.gather(*tasks.values(), return_exceptions=True)
|
|
34
|
+
for k, v in zip(keys, values):
|
|
35
|
+
if isinstance(v, Exception):
|
|
36
|
+
results[k] = {"error": str(v)}
|
|
37
|
+
else:
|
|
38
|
+
results[k] = v
|
|
39
|
+
return results
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def run(**kwargs):
|
|
43
|
+
return asyncio.run(run_async(**kwargs))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import aiohttp
|
|
3
|
+
|
|
4
|
+
CENSYS_TOKEN = os.environ.get("CENSYS_TOKEN", "censys_UJptWhGQ_L57vdqJiF1FQEd1VtTVArqsX")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def check_host(ip: str) -> dict:
|
|
8
|
+
"""Censys API: хосты, сервисы, TLS."""
|
|
9
|
+
if not CENSYS_TOKEN:
|
|
10
|
+
return {"error": "CENSYS_TOKEN not set"}
|
|
11
|
+
url = f"https://search.censys.io/api/v2/hosts/{ip}"
|
|
12
|
+
headers = {"Authorization": f"Bearer {CENSYS_TOKEN}"}
|
|
13
|
+
try:
|
|
14
|
+
async with aiohttp.ClientSession() as session:
|
|
15
|
+
async with session.get(url, headers=headers, timeout=15) as resp:
|
|
16
|
+
if resp.status == 404:
|
|
17
|
+
return {"found": False}
|
|
18
|
+
if resp.status != 200:
|
|
19
|
+
return {"error": f"HTTP {resp.status}"}
|
|
20
|
+
data = await resp.json()
|
|
21
|
+
result = data.get("result", {})
|
|
22
|
+
services = result.get("services", [])
|
|
23
|
+
return {
|
|
24
|
+
"services": [s.get("service_name") for s in services],
|
|
25
|
+
"ports": [s.get("port") for s in services],
|
|
26
|
+
"os": result.get("operating_system", {}).get("product"),
|
|
27
|
+
"country": result.get("location", {}).get("country"),
|
|
28
|
+
}
|
|
29
|
+
except Exception as e:
|
|
30
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def check(email: str) -> dict:
|
|
6
|
+
"""Holehe: проверка email по 120+ платформам."""
|
|
7
|
+
try:
|
|
8
|
+
proc = await asyncio.create_subprocess_exec(
|
|
9
|
+
"holehe", email, "--only-used", "--no-color",
|
|
10
|
+
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
|
11
|
+
)
|
|
12
|
+
stdout, _ = await proc.communicate()
|
|
13
|
+
lines = stdout.decode("utf-8", errors="ignore").splitlines()
|
|
14
|
+
found = [l for l in lines if "[+]" in l]
|
|
15
|
+
return {"found": len(found), "sites": found[:100]}
|
|
16
|
+
except Exception as e:
|
|
17
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from PicImageSearch import Network, Yandex, Google, Bing, Tineye
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def search(image_path: str, engine: str = "yandex") -> dict:
|
|
6
|
+
"""PicImageSearch: обратный поиск по фото."""
|
|
7
|
+
engines = {"yandex": Yandex, "google": Google, "bing": Bing, "tineye": Tineye}
|
|
8
|
+
cls = engines.get(engine.lower())
|
|
9
|
+
if not cls:
|
|
10
|
+
return {"error": f"unknown engine: {engine}"}
|
|
11
|
+
try:
|
|
12
|
+
async with Network() as client:
|
|
13
|
+
searcher = cls(client=client)
|
|
14
|
+
resp = await searcher.search(file=image_path)
|
|
15
|
+
results = []
|
|
16
|
+
for item in resp.raw[:10]:
|
|
17
|
+
results.append({
|
|
18
|
+
"title": getattr(item, "title", None),
|
|
19
|
+
"url": getattr(item, "url", None),
|
|
20
|
+
"thumbnail": getattr(item, "thumbnail", None),
|
|
21
|
+
})
|
|
22
|
+
return {"engine": engine, "results": results}
|
|
23
|
+
except Exception as e:
|
|
24
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import aiohttp
|
|
3
|
+
|
|
4
|
+
from milenium.modules import config
|
|
5
|
+
IPINFO_TOKEN = config.get("IPINFO_TOKEN", "")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def check(ip: str) -> dict:
|
|
9
|
+
"""ipinfo.io: гео, ASN, компания."""
|
|
10
|
+
if not IPINFO_TOKEN:
|
|
11
|
+
return {"error": "IPINFO_TOKEN not set"}
|
|
12
|
+
url = f"https://ipinfo.io/{ip}/json?token={IPINFO_TOKEN}"
|
|
13
|
+
try:
|
|
14
|
+
async with aiohttp.ClientSession() as session:
|
|
15
|
+
async with session.get(url, timeout=15) as resp:
|
|
16
|
+
if resp.status != 200:
|
|
17
|
+
return {"error": f"HTTP {resp.status}"}
|
|
18
|
+
data = await resp.json()
|
|
19
|
+
return {
|
|
20
|
+
"city": data.get("city"),
|
|
21
|
+
"region": data.get("region"),
|
|
22
|
+
"country": data.get("country"),
|
|
23
|
+
"org": data.get("org"),
|
|
24
|
+
"timezone": data.get("timezone"),
|
|
25
|
+
"loc": data.get("loc"),
|
|
26
|
+
}
|
|
27
|
+
except Exception as e:
|
|
28
|
+
return {"error": str(e)}
|
|
@@ -32,7 +32,7 @@ class TeeLogger:
|
|
|
32
32
|
|
|
33
33
|
def _write_header(self):
|
|
34
34
|
ts = datetime.now().isoformat()
|
|
35
|
-
self.file.write(f"\n{'='*60}\n[SESSION START] {ts}\n{'='*60}\n")
|
|
35
|
+
self.file.write(f"\n{'=' * 60}\n[SESSION START] {ts}\n{'=' * 60}\n")
|
|
36
36
|
self.file.flush()
|
|
37
37
|
|
|
38
38
|
def write(self, message):
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def check(username: str) -> dict:
|
|
6
|
+
"""Maigret: поиск по 3000+ сайтам."""
|
|
7
|
+
try:
|
|
8
|
+
proc = await asyncio.create_subprocess_exec(
|
|
9
|
+
"maigret", username, "--json", "simple", "--no-progressbar",
|
|
10
|
+
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
|
11
|
+
)
|
|
12
|
+
stdout, _ = await proc.communicate()
|
|
13
|
+
data = json.loads(stdout.decode("utf-8", errors="ignore"))
|
|
14
|
+
found = [k for k, v in data.items() if v.get("status") == "claimed"]
|
|
15
|
+
return {"found": len(found), "sites": found[:100]}
|
|
16
|
+
except Exception as e:
|
|
17
|
+
return {"error": str(e)}
|
|
@@ -2,21 +2,28 @@ import os
|
|
|
2
2
|
import json
|
|
3
3
|
import psycopg2
|
|
4
4
|
import psycopg2.extras
|
|
5
|
+
from milenium.modules import config
|
|
5
6
|
|
|
6
|
-
DATABASE_URL =
|
|
7
|
+
DATABASE_URL = ""
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
def
|
|
10
|
+
def _get_url():
|
|
11
|
+
global DATABASE_URL
|
|
10
12
|
if not DATABASE_URL:
|
|
13
|
+
DATABASE_URL = config.get("DATABASE_URL", "")
|
|
14
|
+
return DATABASE_URL
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _conn():
|
|
18
|
+
url = _get_url()
|
|
19
|
+
if not url:
|
|
11
20
|
raise RuntimeError("DATABASE_URL not set")
|
|
12
|
-
return psycopg2.connect(
|
|
21
|
+
return psycopg2.connect(url, sslmode="require")
|
|
13
22
|
|
|
14
23
|
|
|
15
24
|
def init():
|
|
16
|
-
"""Создаёт расширенные таблицы."""
|
|
17
25
|
with _conn() as conn:
|
|
18
26
|
with conn.cursor() as cur:
|
|
19
|
-
# Основная таблица результатов
|
|
20
27
|
cur.execute("""
|
|
21
28
|
CREATE TABLE IF NOT EXISTS results (
|
|
22
29
|
id SERIAL PRIMARY KEY,
|
|
@@ -29,7 +36,6 @@ def init():
|
|
|
29
36
|
data JSONB
|
|
30
37
|
)
|
|
31
38
|
""")
|
|
32
|
-
# Отдельная таблица для найденных URL/ссылок
|
|
33
39
|
cur.execute("""
|
|
34
40
|
CREATE TABLE IF NOT EXISTS findings (
|
|
35
41
|
id SERIAL PRIMARY KEY,
|
|
@@ -41,7 +47,6 @@ def init():
|
|
|
41
47
|
meta JSONB
|
|
42
48
|
)
|
|
43
49
|
""")
|
|
44
|
-
# Таблица для утечек
|
|
45
50
|
cur.execute("""
|
|
46
51
|
CREATE TABLE IF NOT EXISTS leaks (
|
|
47
52
|
id SERIAL PRIMARY KEY,
|
|
@@ -53,7 +58,6 @@ def init():
|
|
|
53
58
|
breach TEXT
|
|
54
59
|
)
|
|
55
60
|
""")
|
|
56
|
-
# Таблица сессий
|
|
57
61
|
cur.execute("""
|
|
58
62
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
59
63
|
id SERIAL PRIMARY KEY,
|
|
@@ -66,7 +70,6 @@ def init():
|
|
|
66
70
|
|
|
67
71
|
|
|
68
72
|
def save(command, target, module, data, target_type=None):
|
|
69
|
-
"""Сохраняет результат + разбирает его на findings/leaks."""
|
|
70
73
|
found_count = 0
|
|
71
74
|
if isinstance(data, dict):
|
|
72
75
|
for k, v in data.items():
|
|
@@ -79,10 +82,9 @@ def save(command, target, module, data, target_type=None):
|
|
|
79
82
|
"""INSERT INTO results (command, target, target_type, module, found_count, data)
|
|
80
83
|
VALUES (%s, %s, %s, %s, %s, %s)""",
|
|
81
84
|
(command, target, target_type, module, found_count,
|
|
82
|
-
json.dumps(data, ensure_ascii=False))
|
|
85
|
+
json.dumps(data, ensure_ascii=False, default=str))
|
|
83
86
|
)
|
|
84
87
|
|
|
85
|
-
# Разбираем findings
|
|
86
88
|
if isinstance(data, dict):
|
|
87
89
|
for key, val in data.items():
|
|
88
90
|
if isinstance(val, dict) and "url" in val:
|
|
@@ -90,7 +92,7 @@ def save(command, target, module, data, target_type=None):
|
|
|
90
92
|
"""INSERT INTO findings (target, source, url, found, meta)
|
|
91
93
|
VALUES (%s, %s, %s, %s, %s)""",
|
|
92
94
|
(target, key, val.get("url"), val.get("found", False),
|
|
93
|
-
json.dumps(val, ensure_ascii=False))
|
|
95
|
+
json.dumps(val, ensure_ascii=False, default=str))
|
|
94
96
|
)
|
|
95
97
|
elif isinstance(val, list):
|
|
96
98
|
for item in val:
|
|
@@ -99,10 +101,15 @@ def save(command, target, module, data, target_type=None):
|
|
|
99
101
|
"""INSERT INTO findings (target, source, url, found, meta)
|
|
100
102
|
VALUES (%s, %s, %s, %s, %s)""",
|
|
101
103
|
(target, key, item.get("url"), True,
|
|
102
|
-
json.dumps(item, ensure_ascii=False))
|
|
104
|
+
json.dumps(item, ensure_ascii=False, default=str))
|
|
105
|
+
)
|
|
106
|
+
elif isinstance(item, str) and item.startswith("http"):
|
|
107
|
+
cur.execute(
|
|
108
|
+
"""INSERT INTO findings (target, source, url, found, meta)
|
|
109
|
+
VALUES (%s, %s, %s, %s, %s)""",
|
|
110
|
+
(target, key, item, True, json.dumps({"url": item}))
|
|
103
111
|
)
|
|
104
112
|
|
|
105
|
-
# Разбираем утечки
|
|
106
113
|
if isinstance(data, dict) and "breaches" in data:
|
|
107
114
|
for b in data.get("breaches", []):
|
|
108
115
|
cur.execute(
|
|
@@ -163,19 +170,22 @@ def leaks(target=None, limit=100):
|
|
|
163
170
|
|
|
164
171
|
|
|
165
172
|
def stats():
|
|
166
|
-
|
|
167
|
-
with
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
try:
|
|
174
|
+
with _conn() as conn:
|
|
175
|
+
with conn.cursor() as cur:
|
|
176
|
+
cur.execute("SELECT COUNT(*) FROM results")
|
|
177
|
+
total = cur.fetchone()[0]
|
|
178
|
+
cur.execute("SELECT COUNT(*) FROM findings WHERE found = TRUE")
|
|
179
|
+
findings_count = cur.fetchone()[0]
|
|
180
|
+
cur.execute("SELECT COUNT(*) FROM leaks")
|
|
181
|
+
leaks_count = cur.fetchone()[0]
|
|
182
|
+
cur.execute("SELECT module, COUNT(*) FROM results GROUP BY module")
|
|
183
|
+
by_module = dict(cur.fetchall())
|
|
184
|
+
return {
|
|
185
|
+
"total": total,
|
|
186
|
+
"findings": findings_count,
|
|
187
|
+
"leaks": leaks_count,
|
|
188
|
+
"by_module": by_module,
|
|
189
|
+
}
|
|
190
|
+
except Exception as e:
|
|
191
|
+
return {"total": 0, "findings": 0, "leaks": 0, "by_module": {}, "error": str(e)}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def check(username: str) -> dict:
|
|
6
|
+
"""Sherlock: поиск по 3000+ сетям."""
|
|
7
|
+
try:
|
|
8
|
+
proc = await asyncio.create_subprocess_exec(
|
|
9
|
+
"sherlock", username, "--json",
|
|
10
|
+
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
|
11
|
+
)
|
|
12
|
+
stdout, _ = await proc.communicate()
|
|
13
|
+
data = json.loads(stdout.decode("utf-8", errors="ignore"))
|
|
14
|
+
return {"found": len(data), "sites": list(data.keys())[:100]}
|
|
15
|
+
except Exception as e:
|
|
16
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import aiohttp
|
|
3
|
+
|
|
4
|
+
from milenium.modules import config
|
|
5
|
+
SHODAN_KEY = config.get("SHODAN_API_KEY", "")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def check_ip(ip: str) -> dict:
|
|
9
|
+
"""Shodan API: порты, баннеры, CVE, гео."""
|
|
10
|
+
if not SHODAN_KEY:
|
|
11
|
+
return {"error": "SHODAN_API_KEY not set"}
|
|
12
|
+
url = f"https://api.shodan.io/shodan/host/{ip}?key={SHODAN_KEY}&minify=true"
|
|
13
|
+
try:
|
|
14
|
+
async with aiohttp.ClientSession() as session:
|
|
15
|
+
async with session.get(url, timeout=15) as resp:
|
|
16
|
+
if resp.status == 404:
|
|
17
|
+
return {"found": False}
|
|
18
|
+
if resp.status != 200:
|
|
19
|
+
return {"error": f"HTTP {resp.status}"}
|
|
20
|
+
data = await resp.json()
|
|
21
|
+
return {
|
|
22
|
+
"org": data.get("org"),
|
|
23
|
+
"os": data.get("os"),
|
|
24
|
+
"ports": data.get("ports", []),
|
|
25
|
+
"hostnames": data.get("hostnames", []),
|
|
26
|
+
"vulns": list(data.get("vulns", {}).keys()),
|
|
27
|
+
"country": data.get("country_name"),
|
|
28
|
+
"city": data.get("city"),
|
|
29
|
+
}
|
|
30
|
+
except Exception as e:
|
|
31
|
+
return {"error": str(e)}
|