gyntoolkit 2.1.1__tar.gz → 2.2.0__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.
Files changed (37) hide show
  1. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/PKG-INFO +56 -9
  2. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/README.md +55 -8
  3. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/__init__.py +16 -5
  4. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/brute.py +6 -2
  5. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/cli.py +91 -3
  6. gyntoolkit-2.2.0/gyntoolkit/commands.py +312 -0
  7. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/config.py +2 -0
  8. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/core.py +1 -1
  9. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/export.py +90 -1
  10. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/i18n.py +53 -1
  11. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/recon.py +101 -0
  12. gyntoolkit-2.2.0/gyntoolkit/scan.py +305 -0
  13. gyntoolkit-2.2.0/gyntoolkit/web.py +125 -0
  14. gyntoolkit-2.2.0/gyntoolkit/wordlist.py +103 -0
  15. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit.egg-info/PKG-INFO +56 -9
  16. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit.egg-info/SOURCES.txt +10 -1
  17. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/pyproject.toml +1 -1
  18. gyntoolkit-2.2.0/tests/test_brute.py +30 -0
  19. gyntoolkit-2.2.0/tests/test_commands.py +56 -0
  20. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/tests/test_export.py +35 -0
  21. gyntoolkit-2.2.0/tests/test_scan_parse.py +47 -0
  22. gyntoolkit-2.2.0/tests/test_shodan.py +44 -0
  23. gyntoolkit-2.2.0/tests/test_web.py +67 -0
  24. gyntoolkit-2.2.0/tests/test_wordlist.py +69 -0
  25. gyntoolkit-2.1.1/gyntoolkit/scan.py +0 -150
  26. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/LICENSE +0 -0
  27. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/__main__.py +0 -0
  28. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/ui.py +0 -0
  29. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit/utils.py +0 -0
  30. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit.egg-info/dependency_links.txt +0 -0
  31. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit.egg-info/entry_points.txt +0 -0
  32. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit.egg-info/requires.txt +0 -0
  33. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/gyntoolkit.egg-info/top_level.txt +0 -0
  34. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/setup.cfg +0 -0
  35. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/tests/test_config.py +0 -0
  36. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/tests/test_i18n.py +0 -0
  37. {gyntoolkit-2.1.1 → gyntoolkit-2.2.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gyntoolkit
3
- Version: 2.1.1
3
+ Version: 2.2.0
4
4
  Summary: Canivete suíço de pentest — recon, port scan, CVE lookup e brute force.
5
5
  Author: PH, Fl4vs
6
6
  License-Expression: MIT
@@ -49,7 +49,7 @@ Dynamic: license-file
49
49
  ██████╗██╗ ██╗███╗ ██╗ ████████╗ ██████╗ ██████╗ ██╗ ██╗ ██╗██╗████████╗
50
50
  ██╔════╝╚██╗ ██╔╝████╗ ██║ ╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██║ ██╔╝██║╚══██╔══╝
51
51
  ██║ ███╗╚████╔╝ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║██║ █████╔╝ ██║ ██║
52
- ██║ ██║ ╚██╔╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██║ ██╔═██╗ ██║ ██║ v2.1
52
+ ██║ ██║ ╚██╔╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██║ ██╔═██╗ ██║ ██║ v2.2
53
53
  ╚██████╔╝ ██║ ██║ ╚████║ ██║ ╚██████╔╝╚██████╔╝███████╗██║ ██╗██║ ██║ by: PH,Fl4vs
54
54
  ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
55
55
  ```
@@ -124,23 +124,30 @@ Dynamic: license-file
124
124
  | 9 | **HIBP Breach Check** | Domain → known breaches via Have I Been Pwned |
125
125
  | 10 | **MAC Vendor Lookup** | `api.macvendors.com` — OUI → vendor |
126
126
  | 11 | **Traceroute (TCP)** | `scapy` — hops + RTT (needs privilege) |
127
+ | 12 | **DNS Zone Transfer (AXFR)** | `dnspython` — tries AXFR against each authoritative NS |
128
+ | 13 | **Web Content Discovery** | `aiohttp` — robots.txt / sitemap / security.txt + built-in path wordlist |
129
+ | 14 | **Shodan Host** | Full `api.shodan.io` host lookup (ports, CPEs, CVEs, tags) — needs `api_keys.shodan`; falls back to InternetDB hint without a key |
127
130
 
128
131
  ### `[2]` Brute Force
129
132
 
130
133
  | # | Module | Details |
131
134
  |---|--------|---------|
132
- | 1 | **CUPP wordlist** | Wrapper for `cupp.py -i` — custom wordlist generator |
133
- | 2 | **SSH brute** | `paramiko`, async via `asyncio.to_thread`, concurrency `Semaphore`, delay |
134
- | 3 | **HTTP brute** | `aiohttp` — Basic Auth or form POST with configurable `fail_signature` |
135
+ | 1 | **Native wordlist** | Built-in CUPP-style generator (offline, no external clone) — case/leet variants, years, suffixes, term combos |
136
+ | 2 | **CUPP wordlist** | Optional wrapper for `cupp.py -i` |
137
+ | 3 | **SSH brute** | `paramiko`, async via `asyncio.to_thread`, concurrency `Semaphore`, delay |
138
+ | 4 | **HTTP brute** | `aiohttp` — Basic Auth or form POST with configurable `fail_signature` |
135
139
 
136
- > Every attack requires typing `AUTORIZO` to confirm — no silent bypass.
140
+ > Every attack requires typing the authorization word (`AUTHORIZE` / `AUTORIZO`)
141
+ > to confirm — no silent bypass.
137
142
 
138
143
  ### `[3]` Advanced Scanning
139
144
 
140
145
  - **Port scan** — fast (Top 21 common ports) or full (1–65535)
141
146
  - **SYN scan** (stealth) when privileged, automatic fallback to **TCP connect**
142
147
  - **Banner grabbing** — async, parallel
143
- - **CVE analysis** per service via **NVD API v2.0**
148
+ - **CVE analysis** per service via **NVD API v2.0** — parses product + version
149
+ from the banner and matches by **CPE** (`virtualMatchString`), falling back to
150
+ keyword search; rate-limited and deduplicated per banner
144
151
  - **Host discovery** (ARP scan) by CIDR — e.g. `192.168.0.0/24`
145
152
  - Automatic risk classification based on CVEs found
146
153
 
@@ -203,6 +210,46 @@ An interactive dark CLI opens with a numbered menu. Commands are entered at the
203
210
 
204
211
  ---
205
212
 
213
+ ## Non-interactive CLI (scriptable)
214
+
215
+ Pass a subcommand to run a single action and print the result as JSON on stdout
216
+ (pipe-friendly) — no menu. Omit the subcommand to open the interactive menu.
217
+
218
+ ```bash
219
+ gyntoolkit recon dns example.com --type MX # DNS lookup
220
+ gyntoolkit recon axfr example.com # zone transfer attempt
221
+ gyntoolkit recon webscan http://example.com # web content discovery
222
+ gyntoolkit recon shodan 1.1.1.1 # Shodan host (needs api_keys.shodan)
223
+ gyntoolkit recon ssl example.com --port 443 # TLS cert
224
+ gyntoolkit scan 127.0.0.1 --type fast # port scan + CVEs
225
+ gyntoolkit scan 192.168.0.0/24 # host discovery (CIDR)
226
+ gyntoolkit utils hash "text" --algo sha1
227
+ gyntoolkit utils jwt <token>
228
+ gyntoolkit wordlist --terms alice,fluffy,acme --years 1990,2020 --leet --save wl.txt
229
+ ```
230
+
231
+ Global flags (place after the subcommand): `-o/--output DIR` saves a report,
232
+ `-f/--format {json,html,csv,md}` picks its format, `-q/--quiet` silences stdout,
233
+ `--lang {en,pt}` overrides the language.
234
+
235
+ ```bash
236
+ gyntoolkit scan scanme.example.com -o reports -f md # also save a Markdown report
237
+ gyntoolkit recon geo 1.1.1.1 -o reports -f csv -q # CSV only, no stdout
238
+ ```
239
+
240
+ Active attacks are available but require explicit authorization:
241
+
242
+ ```bash
243
+ gyntoolkit brute ssh HOST --users users.txt --passwords pass.txt --authorize
244
+ gyntoolkit brute http URL --users admin --passwords pass.txt --mode form \
245
+ --user-field user --pass-field pass --fail-signature "Invalid" --authorize
246
+ ```
247
+
248
+ Without `--authorize`, brute subcommands abort — you confirm you have **written
249
+ authorization** for the target.
250
+
251
+ ---
252
+
206
253
  ## Example
207
254
 
208
255
  Target: the **local security lab** (never a third-party system).
@@ -256,8 +303,8 @@ Full walkthrough: [`docs/security-lab.md`](docs/security-lab.md) and
256
303
 
257
304
  ## Reports
258
305
 
259
- After scans and recon lookups, the CLI offers to export results as **JSON** or a
260
- dark-themed **HTML report**. Files are written to
306
+ After scans and recon lookups, the CLI offers to export results as **JSON**, a
307
+ dark-themed **HTML report**, **CSV** or **Markdown**. Files are written to
261
308
  `reports/<module>-<timestamp>.<fmt>` (configurable via `export.dir`; set
262
309
  `export.auto: true` to export without prompting). Details in
263
310
  [`docs/reports.md`](docs/reports.md).
@@ -6,7 +6,7 @@
6
6
  ██████╗██╗ ██╗███╗ ██╗ ████████╗ ██████╗ ██████╗ ██╗ ██╗ ██╗██╗████████╗
7
7
  ██╔════╝╚██╗ ██╔╝████╗ ██║ ╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██║ ██╔╝██║╚══██╔══╝
8
8
  ██║ ███╗╚████╔╝ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║██║ █████╔╝ ██║ ██║
9
- ██║ ██║ ╚██╔╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██║ ██╔═██╗ ██║ ██║ v2.1
9
+ ██║ ██║ ╚██╔╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██║ ██╔═██╗ ██║ ██║ v2.2
10
10
  ╚██████╔╝ ██║ ██║ ╚████║ ██║ ╚██████╔╝╚██████╔╝███████╗██║ ██╗██║ ██║ by: PH,Fl4vs
11
11
  ╚═════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
12
12
  ```
@@ -81,23 +81,30 @@
81
81
  | 9 | **HIBP Breach Check** | Domain → known breaches via Have I Been Pwned |
82
82
  | 10 | **MAC Vendor Lookup** | `api.macvendors.com` — OUI → vendor |
83
83
  | 11 | **Traceroute (TCP)** | `scapy` — hops + RTT (needs privilege) |
84
+ | 12 | **DNS Zone Transfer (AXFR)** | `dnspython` — tries AXFR against each authoritative NS |
85
+ | 13 | **Web Content Discovery** | `aiohttp` — robots.txt / sitemap / security.txt + built-in path wordlist |
86
+ | 14 | **Shodan Host** | Full `api.shodan.io` host lookup (ports, CPEs, CVEs, tags) — needs `api_keys.shodan`; falls back to InternetDB hint without a key |
84
87
 
85
88
  ### `[2]` Brute Force
86
89
 
87
90
  | # | Module | Details |
88
91
  |---|--------|---------|
89
- | 1 | **CUPP wordlist** | Wrapper for `cupp.py -i` — custom wordlist generator |
90
- | 2 | **SSH brute** | `paramiko`, async via `asyncio.to_thread`, concurrency `Semaphore`, delay |
91
- | 3 | **HTTP brute** | `aiohttp` — Basic Auth or form POST with configurable `fail_signature` |
92
+ | 1 | **Native wordlist** | Built-in CUPP-style generator (offline, no external clone) — case/leet variants, years, suffixes, term combos |
93
+ | 2 | **CUPP wordlist** | Optional wrapper for `cupp.py -i` |
94
+ | 3 | **SSH brute** | `paramiko`, async via `asyncio.to_thread`, concurrency `Semaphore`, delay |
95
+ | 4 | **HTTP brute** | `aiohttp` — Basic Auth or form POST with configurable `fail_signature` |
92
96
 
93
- > Every attack requires typing `AUTORIZO` to confirm — no silent bypass.
97
+ > Every attack requires typing the authorization word (`AUTHORIZE` / `AUTORIZO`)
98
+ > to confirm — no silent bypass.
94
99
 
95
100
  ### `[3]` Advanced Scanning
96
101
 
97
102
  - **Port scan** — fast (Top 21 common ports) or full (1–65535)
98
103
  - **SYN scan** (stealth) when privileged, automatic fallback to **TCP connect**
99
104
  - **Banner grabbing** — async, parallel
100
- - **CVE analysis** per service via **NVD API v2.0**
105
+ - **CVE analysis** per service via **NVD API v2.0** — parses product + version
106
+ from the banner and matches by **CPE** (`virtualMatchString`), falling back to
107
+ keyword search; rate-limited and deduplicated per banner
101
108
  - **Host discovery** (ARP scan) by CIDR — e.g. `192.168.0.0/24`
102
109
  - Automatic risk classification based on CVEs found
103
110
 
@@ -160,6 +167,46 @@ An interactive dark CLI opens with a numbered menu. Commands are entered at the
160
167
 
161
168
  ---
162
169
 
170
+ ## Non-interactive CLI (scriptable)
171
+
172
+ Pass a subcommand to run a single action and print the result as JSON on stdout
173
+ (pipe-friendly) — no menu. Omit the subcommand to open the interactive menu.
174
+
175
+ ```bash
176
+ gyntoolkit recon dns example.com --type MX # DNS lookup
177
+ gyntoolkit recon axfr example.com # zone transfer attempt
178
+ gyntoolkit recon webscan http://example.com # web content discovery
179
+ gyntoolkit recon shodan 1.1.1.1 # Shodan host (needs api_keys.shodan)
180
+ gyntoolkit recon ssl example.com --port 443 # TLS cert
181
+ gyntoolkit scan 127.0.0.1 --type fast # port scan + CVEs
182
+ gyntoolkit scan 192.168.0.0/24 # host discovery (CIDR)
183
+ gyntoolkit utils hash "text" --algo sha1
184
+ gyntoolkit utils jwt <token>
185
+ gyntoolkit wordlist --terms alice,fluffy,acme --years 1990,2020 --leet --save wl.txt
186
+ ```
187
+
188
+ Global flags (place after the subcommand): `-o/--output DIR` saves a report,
189
+ `-f/--format {json,html,csv,md}` picks its format, `-q/--quiet` silences stdout,
190
+ `--lang {en,pt}` overrides the language.
191
+
192
+ ```bash
193
+ gyntoolkit scan scanme.example.com -o reports -f md # also save a Markdown report
194
+ gyntoolkit recon geo 1.1.1.1 -o reports -f csv -q # CSV only, no stdout
195
+ ```
196
+
197
+ Active attacks are available but require explicit authorization:
198
+
199
+ ```bash
200
+ gyntoolkit brute ssh HOST --users users.txt --passwords pass.txt --authorize
201
+ gyntoolkit brute http URL --users admin --passwords pass.txt --mode form \
202
+ --user-field user --pass-field pass --fail-signature "Invalid" --authorize
203
+ ```
204
+
205
+ Without `--authorize`, brute subcommands abort — you confirm you have **written
206
+ authorization** for the target.
207
+
208
+ ---
209
+
163
210
  ## Example
164
211
 
165
212
  Target: the **local security lab** (never a third-party system).
@@ -213,8 +260,8 @@ Full walkthrough: [`docs/security-lab.md`](docs/security-lab.md) and
213
260
 
214
261
  ## Reports
215
262
 
216
- After scans and recon lookups, the CLI offers to export results as **JSON** or a
217
- dark-themed **HTML report**. Files are written to
263
+ After scans and recon lookups, the CLI offers to export results as **JSON**, a
264
+ dark-themed **HTML report**, **CSV** or **Markdown**. Files are written to
218
265
  `reports/<module>-<timestamp>.<fmt>` (configurable via `export.dir`; set
219
266
  `export.auto: true` to export without prompting). Details in
220
267
  [`docs/reports.md`](docs/reports.md).
@@ -5,7 +5,7 @@ Este ``__init__`` re-exporta a API pública para manter compatibilidade com
5
5
  ``import gyntoolkit as g`` (usado pelo lab E2E) e pelo entry point do pyproject.
6
6
  """
7
7
 
8
- __version__ = "2.1.1"
8
+ __version__ = "2.2.0"
9
9
 
10
10
  from . import i18n, ui
11
11
  from .brute import (
@@ -29,7 +29,7 @@ from .core import (
29
29
  sanitize_input,
30
30
  show_menu,
31
31
  )
32
- from .export import export_html, export_json, save_report
32
+ from .export import export_csv, export_html, export_json, export_md, save_report
33
33
  from .recon import (
34
34
  DNS_TYPES,
35
35
  TECH_SIGNATURES,
@@ -41,20 +41,25 @@ from .recon import (
41
41
  internetdb_lookup,
42
42
  mac_vendor,
43
43
  reverse_dns,
44
+ shodan_host,
44
45
  ssl_inspect,
45
46
  subdomain_enum,
46
47
  traceroute,
47
48
  whois_lookup,
49
+ zone_transfer,
48
50
  )
49
51
  from .scan import (
50
52
  check_vulnerabilities,
51
53
  connect_scan,
52
54
  get_banner,
53
55
  network_discovery,
56
+ parse_service,
54
57
  perform_scan,
55
58
  syn_scan,
56
59
  )
57
60
  from .utils import b64_decode, b64_encode, hash_file, hash_text, jwt_decode
61
+ from .web import COMMON_PATHS, web_discovery
62
+ from .wordlist import generate_wordlist
58
63
 
59
64
  __all__ = [
60
65
  "__version__",
@@ -63,18 +68,24 @@ __all__ = [
63
68
  "clear_screen", "is_admin_windows", "sanitize_input", "show_menu",
64
69
  # scan
65
70
  "syn_scan", "connect_scan", "get_banner", "check_vulnerabilities",
66
- "network_discovery", "perform_scan",
71
+ "parse_service", "network_discovery", "perform_scan",
67
72
  # recon
68
73
  "whois_lookup", "dns_lookup", "geo_ip", "reverse_dns", "subdomain_enum",
69
74
  "ssl_inspect", "http_fingerprint", "internetdb_lookup", "hibp_breaches",
70
- "mac_vendor", "traceroute", "escolher_tipo_dns", "DNS_TYPES", "TECH_SIGNATURES",
75
+ "mac_vendor", "traceroute", "zone_transfer", "shodan_host",
76
+ "escolher_tipo_dns", "DNS_TYPES", "TECH_SIGNATURES",
77
+ # web discovery
78
+ "web_discovery", "COMMON_PATHS",
79
+ # wordlist
80
+ "generate_wordlist",
71
81
  # brute
72
82
  "print_ethical_warning", "load_wordlist", "cupp_generate",
73
83
  "ssh_bruteforce", "http_bruteforce",
74
84
  # utils
75
85
  "hash_text", "hash_file", "b64_encode", "b64_decode", "jwt_decode",
76
86
  # config / export / ui
77
- "CONFIG", "DEFAULTS", "load_config", "save_report", "export_json", "export_html", "ui", "i18n",
87
+ "CONFIG", "DEFAULTS", "load_config", "save_report",
88
+ "export_json", "export_html", "export_csv", "export_md", "ui", "i18n",
78
89
  # cli
79
90
  "main_flow", "main_entry",
80
91
  ]
@@ -21,7 +21,9 @@ def print_ethical_warning(action: str) -> bool:
21
21
  print(f"{Fore.RED}{i18n.t('brute.warn_crime')}{Style.RESET_ALL}")
22
22
  print(f"{Fore.RED}{'=' * 60}{Style.RESET_ALL}")
23
23
  confirm = input(f"{Fore.YELLOW}{i18n.t('brute.confirm')}{Style.RESET_ALL}").strip()
24
- return confirm == "AUTORIZO"
24
+ # Aceita a palavra localizada (en/pt) e o legado "AUTORIZO" como alias.
25
+ accepted = {i18n.t("brute.confirm_word").strip().upper(), "AUTORIZO"}
26
+ return confirm.upper() in accepted
25
27
 
26
28
 
27
29
  def load_wordlist(path: str) -> list[str]:
@@ -158,7 +160,9 @@ async def http_bruteforce(
158
160
  if mode == "basic":
159
161
  auth = aiohttp.BasicAuth(u, p)
160
162
  async with session.get(url, auth=auth) as r:
161
- ok = r.status not in (401, 403)
163
+ # Basic Auth: sucesso = 2xx. 401/403 = falha; 404/5xx
164
+ # NÃO são credencial válida (evita falso-positivo).
165
+ ok = 200 <= r.status < 300
162
166
  else:
163
167
  payload = {user_field: u, pass_field: p}
164
168
  async with session.post(url, data=payload, allow_redirects=False) as r:
@@ -29,13 +29,17 @@ from .recon import (
29
29
  internetdb_lookup,
30
30
  mac_vendor,
31
31
  reverse_dns,
32
+ shodan_host,
32
33
  ssl_inspect,
33
34
  subdomain_enum,
34
35
  traceroute,
35
36
  whois_lookup,
37
+ zone_transfer,
36
38
  )
37
39
  from .scan import network_discovery, perform_scan
38
40
  from .utils import b64_decode, b64_encode, hash_file, hash_text, jwt_decode
41
+ from .web import web_discovery
42
+ from .wordlist import generate_wordlist
39
43
 
40
44
  # Valores canônicos de scan_type (DATA, usados por perform_scan). Neutros de
41
45
  # idioma; entradas localizadas (en/pt) são mapeadas por _normalize_scan_type.
@@ -113,6 +117,9 @@ async def main_flow():
113
117
  i18n.t("menu.info.hibp"),
114
118
  i18n.t("menu.info.macvendor"),
115
119
  i18n.t("menu.info.traceroute"),
120
+ i18n.t("menu.info.axfr"),
121
+ i18n.t("menu.info.webscan"),
122
+ i18n.t("menu.info.shodan"),
116
123
  ])
117
124
 
118
125
  if sub_choice == 1:
@@ -266,17 +273,84 @@ async def main_flow():
266
273
  _offer_export({"target": target, "dport": dport, "hops": hops},
267
274
  f"traceroute-{target}", f"Traceroute {target}")
268
275
 
276
+ elif sub_choice == 12:
277
+ domain = sanitize_input(input(_cyan("prompt.root_domain")), r"[A-Za-z0-9.-]")
278
+ with ui.status(i18n.t("status.axfr", d=domain)):
279
+ r = zone_transfer(domain)
280
+ if "erro" in r:
281
+ ui.error(r["erro"])
282
+ else:
283
+ if r.get("vulnerable"):
284
+ ui.error(i18n.t("msg.axfr_vulnerable"))
285
+ else:
286
+ ui.success(i18n.t("msg.axfr_safe"))
287
+ ui.print_kv(i18n.t("label.axfr", d=domain), r["nameservers"])
288
+ _offer_export(r, f"axfr-{domain}", f"Zone Transfer {domain}")
289
+
290
+ elif sub_choice == 13:
291
+ url = input(f"\n{Fore.CYAN}{i18n.t('prompt.url_http')}{Style.RESET_ALL}").strip()
292
+ with ui.status(i18n.t("status.webscan", u=url)):
293
+ r = await web_discovery(url)
294
+ if "erro" in r:
295
+ ui.error(r["erro"])
296
+ elif not r["found"]:
297
+ ui.notice(i18n.t("msg.webscan_none", u=url))
298
+ else:
299
+ rows = [
300
+ (f["status"], f["path"], f["length"], f.get("location") or "—")
301
+ for f in r["found"]
302
+ ]
303
+ ui.print_table(i18n.t("label.webscan", u=r["base"], n=r["total"]),
304
+ i18n.t("table.webscan").split("|"), rows)
305
+ _offer_export(r, f"webscan-{url}", f"Web Discovery {url}")
306
+
307
+ elif sub_choice == 14:
308
+ target = sanitize_input(input(_cyan("prompt.ip_or_domain")), r"[A-Za-z0-9.:-]")
309
+ key = CONFIG.get("api_keys", {}).get("shodan", "")
310
+ with ui.status(i18n.t("status.shodan", t=target)):
311
+ info = shodan_host(target, api_key=key)
312
+ if "erro" in info:
313
+ ui.error(info["erro"])
314
+ elif "info" in info:
315
+ ui.notice(info["info"])
316
+ else:
317
+ ui.print_kv(i18n.t("label.shodan", t=target), info)
318
+ _offer_export(info, f"shodan-{target}", f"Shodan {target}")
319
+
269
320
  elif choice == 2: # Brute Force
270
321
  b = CONFIG["brute"]
271
322
  sub_choice = show_menu(i18n.t("menu.brute.title"), [
272
323
  i18n.t("menu.brute.cupp"),
273
324
  i18n.t("menu.brute.ssh"),
274
325
  i18n.t("menu.brute.http"),
326
+ i18n.t("menu.brute.genwl"),
275
327
  ])
276
328
 
277
329
  if sub_choice == 1:
278
330
  cupp_generate()
279
331
 
332
+ elif sub_choice == 4:
333
+ terms_raw = input(f"\n{Fore.CYAN}{i18n.t('prompt.wl_terms')}{Style.RESET_ALL}")
334
+ terms = [t for t in terms_raw.split(",") if t.strip()]
335
+ if not terms:
336
+ ui.error(i18n.t("msg.wl_empty"))
337
+ _pause()
338
+ continue
339
+ years_raw = input(f"{Fore.CYAN}{i18n.t('prompt.wl_years')}{Style.RESET_ALL}")
340
+ years = [y for y in years_raw.split(",") if y.strip()]
341
+ leet = input(f"{Fore.CYAN}{i18n.t('prompt.wl_leet')}{Style.RESET_ALL}").strip().lower() in ("y", "s")
342
+ save = input(f"{Fore.CYAN}{i18n.t('prompt.wl_save')}{Style.RESET_ALL}").strip()
343
+ words = generate_wordlist(terms, years=years, use_leet=leet)
344
+ ui.success(i18n.t("msg.wl_generated", n=len(words)))
345
+ if save:
346
+ try:
347
+ Path(save).expanduser().write_text("\n".join(words) + "\n", encoding="utf-8")
348
+ ui.success(i18n.t("msg.wl_saved", path=save))
349
+ except OSError as e:
350
+ ui.error(i18n.t("msg.export_fail", err=e))
351
+ else:
352
+ print("\n".join(words))
353
+
280
354
  elif sub_choice == 2:
281
355
  if not print_ethical_warning("Brute-force SSH"):
282
356
  ui.error(i18n.t("msg.auth_not_confirmed"))
@@ -371,9 +445,13 @@ async def main_flow():
371
445
  targets = [target]
372
446
 
373
447
  report = {}
448
+ scan_concurrency = CONFIG["scan"].get("concurrency", 100)
449
+ nvd_key = CONFIG.get("api_keys", {}).get("nvd", "")
374
450
  for host in targets:
375
451
  with ui.status(i18n.t("status.scanning", h=host)):
376
- results = await perform_scan(host, scan_type)
452
+ results = await perform_scan(
453
+ host, scan_type, concurrency=scan_concurrency, nvd_api_key=nvd_key
454
+ )
377
455
  report[host] = results
378
456
  rows = [
379
457
  (port, _scan_service_label(data["service"]), _scan_risk_label(data["risco"]),
@@ -437,8 +515,18 @@ async def main_flow():
437
515
 
438
516
 
439
517
  def main_entry() -> None:
440
- """Entry point CLI (usado por pyproject scripts)."""
441
- i18n.set_lang(i18n.resolve_lang(CONFIG.get("ui", {}).get("lang")))
518
+ """Entry point CLI (usado por pyproject scripts).
519
+
520
+ Com subcomando (``gyntoolkit recon dns ...``) roda a CLI não-interativa e
521
+ sai. Sem subcomando, abre o menu interativo clássico.
522
+ """
523
+ from . import commands
524
+
525
+ rc = commands.run()
526
+ if rc != -1: # subcomando executado (ou erro) → não abre o menu
527
+ sys.exit(rc)
528
+
529
+ # Sem subcomando: menu interativo. i18n já foi resolvido em commands.run().
442
530
  try:
443
531
  if os.name == 'posix' and os.geteuid() != 0:
444
532
  print(f"\n{Fore.RED}{i18n.t('warn.need_root')}{Style.RESET_ALL}")