gyntoolkit 2.1.1__py3-none-any.whl
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.
- gyntoolkit/__init__.py +80 -0
- gyntoolkit/__main__.py +7 -0
- gyntoolkit/brute.py +179 -0
- gyntoolkit/cli.py +451 -0
- gyntoolkit/config.py +109 -0
- gyntoolkit/core.py +86 -0
- gyntoolkit/export.py +139 -0
- gyntoolkit/i18n.py +487 -0
- gyntoolkit/recon.py +358 -0
- gyntoolkit/scan.py +150 -0
- gyntoolkit/ui.py +132 -0
- gyntoolkit/utils.py +61 -0
- gyntoolkit-2.1.1.dist-info/METADATA +386 -0
- gyntoolkit-2.1.1.dist-info/RECORD +18 -0
- gyntoolkit-2.1.1.dist-info/WHEEL +5 -0
- gyntoolkit-2.1.1.dist-info/entry_points.txt +2 -0
- gyntoolkit-2.1.1.dist-info/licenses/LICENSE +21 -0
- gyntoolkit-2.1.1.dist-info/top_level.txt +1 -0
gyntoolkit/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""GynToolkit — canivete suíço de pentest (recon, scan, brute, utils).
|
|
3
|
+
|
|
4
|
+
Este ``__init__`` re-exporta a API pública para manter compatibilidade com
|
|
5
|
+
``import gyntoolkit as g`` (usado pelo lab E2E) e pelo entry point do pyproject.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "2.1.1"
|
|
9
|
+
|
|
10
|
+
from . import i18n, ui
|
|
11
|
+
from .brute import (
|
|
12
|
+
cupp_generate,
|
|
13
|
+
http_bruteforce,
|
|
14
|
+
load_wordlist,
|
|
15
|
+
print_ethical_warning,
|
|
16
|
+
ssh_bruteforce,
|
|
17
|
+
)
|
|
18
|
+
from .cli import main_entry, main_flow
|
|
19
|
+
from .config import CONFIG, DEFAULTS, load_config
|
|
20
|
+
from .core import (
|
|
21
|
+
LOGO,
|
|
22
|
+
MAX_THREADS,
|
|
23
|
+
PROJECT_ROOT,
|
|
24
|
+
PROMPT,
|
|
25
|
+
TOP_PORTS,
|
|
26
|
+
clear_screen,
|
|
27
|
+
is_admin_windows,
|
|
28
|
+
log,
|
|
29
|
+
sanitize_input,
|
|
30
|
+
show_menu,
|
|
31
|
+
)
|
|
32
|
+
from .export import export_html, export_json, save_report
|
|
33
|
+
from .recon import (
|
|
34
|
+
DNS_TYPES,
|
|
35
|
+
TECH_SIGNATURES,
|
|
36
|
+
dns_lookup,
|
|
37
|
+
escolher_tipo_dns,
|
|
38
|
+
geo_ip,
|
|
39
|
+
hibp_breaches,
|
|
40
|
+
http_fingerprint,
|
|
41
|
+
internetdb_lookup,
|
|
42
|
+
mac_vendor,
|
|
43
|
+
reverse_dns,
|
|
44
|
+
ssl_inspect,
|
|
45
|
+
subdomain_enum,
|
|
46
|
+
traceroute,
|
|
47
|
+
whois_lookup,
|
|
48
|
+
)
|
|
49
|
+
from .scan import (
|
|
50
|
+
check_vulnerabilities,
|
|
51
|
+
connect_scan,
|
|
52
|
+
get_banner,
|
|
53
|
+
network_discovery,
|
|
54
|
+
perform_scan,
|
|
55
|
+
syn_scan,
|
|
56
|
+
)
|
|
57
|
+
from .utils import b64_decode, b64_encode, hash_file, hash_text, jwt_decode
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"__version__",
|
|
61
|
+
# core
|
|
62
|
+
"LOGO", "PROMPT", "TOP_PORTS", "MAX_THREADS", "PROJECT_ROOT", "log",
|
|
63
|
+
"clear_screen", "is_admin_windows", "sanitize_input", "show_menu",
|
|
64
|
+
# scan
|
|
65
|
+
"syn_scan", "connect_scan", "get_banner", "check_vulnerabilities",
|
|
66
|
+
"network_discovery", "perform_scan",
|
|
67
|
+
# recon
|
|
68
|
+
"whois_lookup", "dns_lookup", "geo_ip", "reverse_dns", "subdomain_enum",
|
|
69
|
+
"ssl_inspect", "http_fingerprint", "internetdb_lookup", "hibp_breaches",
|
|
70
|
+
"mac_vendor", "traceroute", "escolher_tipo_dns", "DNS_TYPES", "TECH_SIGNATURES",
|
|
71
|
+
# brute
|
|
72
|
+
"print_ethical_warning", "load_wordlist", "cupp_generate",
|
|
73
|
+
"ssh_bruteforce", "http_bruteforce",
|
|
74
|
+
# utils
|
|
75
|
+
"hash_text", "hash_file", "b64_encode", "b64_decode", "jwt_decode",
|
|
76
|
+
# config / export / ui
|
|
77
|
+
"CONFIG", "DEFAULTS", "load_config", "save_report", "export_json", "export_html", "ui", "i18n",
|
|
78
|
+
# cli
|
|
79
|
+
"main_flow", "main_entry",
|
|
80
|
+
]
|
gyntoolkit/__main__.py
ADDED
gyntoolkit/brute.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Brute force: aviso ético, wordlists, CUPP, SSH e HTTP (basic/form)."""
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from colorama import Fore, Style
|
|
11
|
+
|
|
12
|
+
from . import i18n
|
|
13
|
+
from .core import PROJECT_ROOT, log
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def print_ethical_warning(action: str) -> bool:
|
|
17
|
+
"""Alerta ético antes de operação intrusiva. Retorna True se autorizado."""
|
|
18
|
+
print(f"\n{Fore.RED}{'=' * 60}{Style.RESET_ALL}")
|
|
19
|
+
print(f"{Fore.RED}{i18n.t('brute.warn_active', action=action)}{Style.RESET_ALL}")
|
|
20
|
+
print(f"{Fore.RED}{i18n.t('brute.warn_authorized')}{Style.RESET_ALL}")
|
|
21
|
+
print(f"{Fore.RED}{i18n.t('brute.warn_crime')}{Style.RESET_ALL}")
|
|
22
|
+
print(f"{Fore.RED}{'=' * 60}{Style.RESET_ALL}")
|
|
23
|
+
confirm = input(f"{Fore.YELLOW}{i18n.t('brute.confirm')}{Style.RESET_ALL}").strip()
|
|
24
|
+
return confirm == "AUTORIZO"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_wordlist(path: str) -> list[str]:
|
|
28
|
+
"""Carrega wordlist do disco, deduplicando e ignorando linhas vazias."""
|
|
29
|
+
p = Path(path).expanduser()
|
|
30
|
+
if not p.is_file():
|
|
31
|
+
print(f"{Fore.RED}{i18n.t('brute.wordlist_not_found', path=p)}{Style.RESET_ALL}")
|
|
32
|
+
return []
|
|
33
|
+
try:
|
|
34
|
+
with p.open("r", encoding="utf-8", errors="ignore") as f:
|
|
35
|
+
words = [w.strip() for w in f if w.strip()]
|
|
36
|
+
return list(dict.fromkeys(words))
|
|
37
|
+
except OSError as e:
|
|
38
|
+
log.error("Falha ao ler wordlist %s: %s", p, e)
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def cupp_generate() -> None:
|
|
43
|
+
"""Chama CUPP interativo p/ gerar wordlist customizada."""
|
|
44
|
+
cupp_paths = [
|
|
45
|
+
PROJECT_ROOT / "cupp" / "cupp.py",
|
|
46
|
+
Path.cwd() / "cupp" / "cupp.py",
|
|
47
|
+
]
|
|
48
|
+
cupp = next((p for p in cupp_paths if p.is_file()), None)
|
|
49
|
+
if not cupp:
|
|
50
|
+
print(f"{Fore.RED}{i18n.t('brute.cupp_not_found')}{Style.RESET_ALL}")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
python_exe = shutil.which("python") or shutil.which("python3") or sys.executable
|
|
54
|
+
try:
|
|
55
|
+
subprocess.run([python_exe, str(cupp), "-i"], check=False)
|
|
56
|
+
except OSError as e:
|
|
57
|
+
log.error("CUPP execução falhou: %s", e)
|
|
58
|
+
print(f"{Fore.RED}{i18n.t('brute.cupp_err', err=e)}{Style.RESET_ALL}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def _ssh_try(host: str, port: int, user: str, password: str, timeout: int) -> tuple[str, str] | None:
|
|
62
|
+
"""Tenta uma credencial SSH. Retorna (user, pass) se sucesso."""
|
|
63
|
+
import paramiko
|
|
64
|
+
|
|
65
|
+
def _attempt() -> bool:
|
|
66
|
+
client = paramiko.SSHClient()
|
|
67
|
+
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
68
|
+
try:
|
|
69
|
+
client.connect(
|
|
70
|
+
host, port=port, username=user, password=password,
|
|
71
|
+
timeout=timeout, allow_agent=False, look_for_keys=False,
|
|
72
|
+
banner_timeout=timeout, auth_timeout=timeout,
|
|
73
|
+
)
|
|
74
|
+
return True
|
|
75
|
+
except paramiko.AuthenticationException:
|
|
76
|
+
return False
|
|
77
|
+
except (paramiko.SSHException, OSError, EOFError) as e:
|
|
78
|
+
log.debug("SSH %s@%s:%s erro: %s", user, host, port, e)
|
|
79
|
+
return False
|
|
80
|
+
finally:
|
|
81
|
+
client.close()
|
|
82
|
+
|
|
83
|
+
ok = await asyncio.to_thread(_attempt)
|
|
84
|
+
return (user, password) if ok else None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def ssh_bruteforce(
|
|
88
|
+
host: str,
|
|
89
|
+
users: list[str],
|
|
90
|
+
passwords: list[str],
|
|
91
|
+
port: int = 22,
|
|
92
|
+
workers: int = 8,
|
|
93
|
+
delay: float = 0.1,
|
|
94
|
+
timeout: int = 5,
|
|
95
|
+
) -> list[tuple[str, str]]:
|
|
96
|
+
"""Brute-force SSH assíncrono com controle de concorrência."""
|
|
97
|
+
try:
|
|
98
|
+
import paramiko # noqa: F401
|
|
99
|
+
except ImportError:
|
|
100
|
+
print(f"{Fore.RED}{i18n.t('brute.paramiko_missing')}{Style.RESET_ALL}")
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
sem = asyncio.Semaphore(workers)
|
|
104
|
+
found: list[tuple[str, str]] = []
|
|
105
|
+
total = len(users) * len(passwords)
|
|
106
|
+
tried = 0
|
|
107
|
+
|
|
108
|
+
async def _guarded(u: str, p: str):
|
|
109
|
+
nonlocal tried
|
|
110
|
+
async with sem:
|
|
111
|
+
await asyncio.sleep(delay)
|
|
112
|
+
result = await _ssh_try(host, port, u, p, timeout)
|
|
113
|
+
tried += 1
|
|
114
|
+
if tried % 25 == 0:
|
|
115
|
+
print(f"{Fore.CYAN}{i18n.t('brute.progress', tried=tried, total=total)}{Style.RESET_ALL}")
|
|
116
|
+
if result:
|
|
117
|
+
found.append(result)
|
|
118
|
+
print(f"{Fore.GREEN}{i18n.t('brute.ssh_valid', user=u, pwd=p)}{Style.RESET_ALL}")
|
|
119
|
+
|
|
120
|
+
tasks = [_guarded(u, p) for u in users for p in passwords]
|
|
121
|
+
await asyncio.gather(*tasks)
|
|
122
|
+
return found
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def http_bruteforce(
|
|
126
|
+
url: str,
|
|
127
|
+
users: list[str],
|
|
128
|
+
passwords: list[str],
|
|
129
|
+
mode: str = "basic",
|
|
130
|
+
user_field: str = "username",
|
|
131
|
+
pass_field: str = "password",
|
|
132
|
+
fail_signature: str = "",
|
|
133
|
+
workers: int = 10,
|
|
134
|
+
delay: float = 0.1,
|
|
135
|
+
timeout: int = 10,
|
|
136
|
+
) -> list[tuple[str, str]]:
|
|
137
|
+
"""Brute-force HTTP (basic-auth ou form POST)."""
|
|
138
|
+
try:
|
|
139
|
+
import aiohttp
|
|
140
|
+
except ImportError:
|
|
141
|
+
print(f"{Fore.RED}{i18n.t('brute.aiohttp_missing')}{Style.RESET_ALL}")
|
|
142
|
+
return []
|
|
143
|
+
|
|
144
|
+
sem = asyncio.Semaphore(workers)
|
|
145
|
+
found: list[tuple[str, str]] = []
|
|
146
|
+
total = len(users) * len(passwords)
|
|
147
|
+
tried = 0
|
|
148
|
+
|
|
149
|
+
timeout_cfg = aiohttp.ClientTimeout(total=timeout)
|
|
150
|
+
connector = aiohttp.TCPConnector(limit=workers, ssl=False)
|
|
151
|
+
|
|
152
|
+
async with aiohttp.ClientSession(timeout=timeout_cfg, connector=connector) as session:
|
|
153
|
+
async def _attempt(u: str, p: str):
|
|
154
|
+
nonlocal tried
|
|
155
|
+
async with sem:
|
|
156
|
+
await asyncio.sleep(delay)
|
|
157
|
+
try:
|
|
158
|
+
if mode == "basic":
|
|
159
|
+
auth = aiohttp.BasicAuth(u, p)
|
|
160
|
+
async with session.get(url, auth=auth) as r:
|
|
161
|
+
ok = r.status not in (401, 403)
|
|
162
|
+
else:
|
|
163
|
+
payload = {user_field: u, pass_field: p}
|
|
164
|
+
async with session.post(url, data=payload, allow_redirects=False) as r:
|
|
165
|
+
body = await r.text()
|
|
166
|
+
ok = r.status in (200, 302) and (not fail_signature or fail_signature not in body)
|
|
167
|
+
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
|
168
|
+
log.debug("HTTP %s %s:%s erro: %s", url, u, p, e)
|
|
169
|
+
ok = False
|
|
170
|
+
|
|
171
|
+
tried += 1
|
|
172
|
+
if tried % 25 == 0:
|
|
173
|
+
print(f"{Fore.CYAN}{i18n.t('brute.progress', tried=tried, total=total)}{Style.RESET_ALL}")
|
|
174
|
+
if ok:
|
|
175
|
+
found.append((u, p))
|
|
176
|
+
print(f"{Fore.GREEN}{i18n.t('brute.http_valid', user=u, pwd=p)}{Style.RESET_ALL}")
|
|
177
|
+
|
|
178
|
+
await asyncio.gather(*[_attempt(u, p) for u in users for p in passwords])
|
|
179
|
+
return found
|