printerxpl-forge 6.2.0__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.
- nse/README.md +204 -0
- nse/__init__.py +6 -0
- nse/install_nse.py +412 -0
- nse/lib/printerxpl.lua +238 -0
- nse/scripts/cups-info.nse +74 -0
- nse/scripts/cups-queue-info.nse +43 -0
- nse/scripts/hp-printers-cve-2022-1026.nse +121 -0
- nse/scripts/http-device-mac.nse +107 -0
- nse/scripts/http-hp-ilo-info.nse +121 -0
- nse/scripts/http-info-xerox-enum.nse +101 -0
- nse/scripts/http-vuln-cve2022-1026.nse +158 -0
- nse/scripts/lexmark-config.nse +89 -0
- nse/scripts/pjl-ready-message.nse +106 -0
- nse/scripts/printer-banner.nse +217 -0
- nse/scripts/printer-cups-rce.nse +189 -0
- nse/scripts/printer-cve-detect.nse +279 -0
- nse/scripts/printer-discover.nse +205 -0
- nse/scripts/printer-firmware-exposed.nse +219 -0
- nse/scripts/printer-hp-pjl.nse +192 -0
- nse/scripts/printer-http-ews.nse +293 -0
- nse/scripts/printer-ipp-info.nse +235 -0
- nse/scripts/printer-lexmark-ipp.nse +203 -0
- nse/scripts/printer-passback.nse +204 -0
- nse/scripts/printer-pjl-info.nse +146 -0
- nse/scripts/printer-printnightmare.nse +211 -0
- nse/scripts/printer-snmp-info.nse +176 -0
- nse/scripts/printer-vuln-check.nse +256 -0
- nse/scripts/snmp-device-mac.nse +93 -0
- nse/scripts/snmp-info.nse +146 -0
- nse/scripts/snmp-sysdescr.nse +70 -0
- printerxpl_forge-6.2.0.dist-info/METADATA +919 -0
- printerxpl_forge-6.2.0.dist-info/RECORD +97 -0
- printerxpl_forge-6.2.0.dist-info/WHEEL +5 -0
- printerxpl_forge-6.2.0.dist-info/entry_points.txt +4 -0
- printerxpl_forge-6.2.0.dist-info/licenses/LICENSE +21 -0
- printerxpl_forge-6.2.0.dist-info/top_level.txt +4 -0
- src/assets/fonts/gunplay.pfa +1671 -0
- src/assets/fonts/kshandwrt.pfa +315 -0
- src/assets/fonts/laksoner.pfa +2402 -0
- src/assets/fonts/paintcans.pfa +9699 -0
- src/assets/fonts/stencilod.pfa +4076 -0
- src/assets/fonts/takecover.pfa +26138 -0
- src/assets/fonts/topsecret.pfa +6652 -0
- src/assets/fonts/whoa.pfa +773 -0
- src/assets/mibs/HOST-RESOURCES-MIB +1540 -0
- src/assets/mibs/Printer-MIB +4389 -0
- src/assets/mibs/README.md +9 -0
- src/assets/mibs/SNMPv2-MIB +854 -0
- src/assets/overlays/hacker.eps +596 -0
- src/assets/overlays/smiley.eps +214 -0
- src/assets/overlays/smiley2.eps +240 -0
- src/core/attack_orchestrator.py +1025 -0
- src/core/capabilities.py +323 -0
- src/core/destructive_audit.py +430 -0
- src/core/discovery.py +488 -0
- src/core/osdetect.py +74 -0
- src/core/poly_runner.py +579 -0
- src/core/printer.py +1426 -0
- src/main.py +2134 -0
- src/modules/install_printer.py +318 -0
- src/modules/login_bruteforce.py +852 -0
- src/modules/pcl.py +506 -0
- src/modules/pjl.py +3575 -0
- src/modules/print_job.py +1290 -0
- src/modules/ps.py +1102 -0
- src/payloads/__init__.py +98 -0
- src/payloads/assets/overlays/notice.eps +9 -0
- src/protocols/__init__.py +19 -0
- src/protocols/firmware.py +738 -0
- src/protocols/ipp.py +216 -0
- src/protocols/ipp_attacks.py +609 -0
- src/protocols/lpd.py +141 -0
- src/protocols/network_map.py +1004 -0
- src/protocols/raw.py +173 -0
- src/protocols/smb.py +359 -0
- src/protocols/ssrf_pivot.py +427 -0
- src/protocols/storage.py +587 -0
- src/ui/__init__.py +6 -0
- src/ui/interactive.py +742 -0
- src/ui/spinner.py +112 -0
- src/ui/tables.py +132 -0
- src/utils/banner_grabber.py +852 -0
- src/utils/codebook.py +456 -0
- src/utils/config.py +522 -0
- src/utils/cve_loader.py +158 -0
- src/utils/default_creds.py +134 -0
- src/utils/discovery_online.py +1327 -0
- src/utils/exploit_manager.py +805 -0
- src/utils/fuzzer.py +220 -0
- src/utils/helper.py +732 -0
- src/utils/local_printers.py +307 -0
- src/utils/ml_engine.py +491 -0
- src/utils/operators.py +474 -0
- src/utils/ports.py +234 -0
- src/utils/vuln_scanner.py +823 -0
- src/utils/wordlist_loader.py +412 -0
- src/version.py +36 -0
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
PrinterXPL-Forge — Exploit Manager
|
|
5
|
+
================================
|
|
6
|
+
Loads, indexes, matches and executes exploits from the xpl/ directory.
|
|
7
|
+
|
|
8
|
+
Directory structure expected:
|
|
9
|
+
xpl/
|
|
10
|
+
├── index.json # Auto-regenerated master index
|
|
11
|
+
├── edb-15631/
|
|
12
|
+
│ ├── metadata.json # Exploit metadata
|
|
13
|
+
│ └── exploit.py # check() + run() functions
|
|
14
|
+
├── custom/
|
|
15
|
+
│ ├── TEMPLATE.py # Reference template
|
|
16
|
+
│ └── my_exploit.py # User-created exploit
|
|
17
|
+
└── ...
|
|
18
|
+
|
|
19
|
+
Exploit contract (exploit.py must export):
|
|
20
|
+
- check(host, port, timeout) -> bool # non-destructive vulnerability check
|
|
21
|
+
- run(host, port, timeout, **opts) -> dict # exploit execution
|
|
22
|
+
- METADATA = {...} (optional — falls back to metadata.json)
|
|
23
|
+
|
|
24
|
+
Integration points:
|
|
25
|
+
- Called by vuln_scanner / banner_grabber to show matched exploits on scan
|
|
26
|
+
- Called by main.py for --xpl-list, --xpl-run, --xpl-check, --xpl-update
|
|
27
|
+
"""
|
|
28
|
+
# Author : Andre Henrique (@mrhenrike)
|
|
29
|
+
# GitHub : https://github.com/mrhenrike
|
|
30
|
+
# LinkedIn : https://linkedin.com/in/mrhenrike
|
|
31
|
+
# X/Twitter : https://x.com/mrhenrike
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import importlib.util
|
|
36
|
+
import json
|
|
37
|
+
import logging
|
|
38
|
+
import os
|
|
39
|
+
import re
|
|
40
|
+
import sys
|
|
41
|
+
import time
|
|
42
|
+
from dataclasses import dataclass, field
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
45
|
+
|
|
46
|
+
_log = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
# ── Paths ──────────────────────────────────────────────────────────────────────
|
|
49
|
+
# xpl/ sits at project root (one level above src/)
|
|
50
|
+
_SRC_DIR = Path(__file__).resolve().parent.parent # src/
|
|
51
|
+
_ROOT_DIR = _SRC_DIR.parent # project root
|
|
52
|
+
XPL_DIR = _ROOT_DIR / 'xpl'
|
|
53
|
+
INDEX_FILE = XPL_DIR / 'index.json'
|
|
54
|
+
|
|
55
|
+
# Severity sort order
|
|
56
|
+
_SEV_ORDER = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3, 'info': 4}
|
|
57
|
+
|
|
58
|
+
# ANSI colours
|
|
59
|
+
_RED = '\033[1;31m'
|
|
60
|
+
_YEL = '\033[1;33m'
|
|
61
|
+
_GRN = '\033[0;32m'
|
|
62
|
+
_CYN = '\033[1;36m'
|
|
63
|
+
_DIM = '\033[2;37m'
|
|
64
|
+
_RST = '\033[0m'
|
|
65
|
+
_SEV_CLR = {
|
|
66
|
+
'critical': '\033[1;31m',
|
|
67
|
+
'high': '\033[0;31m',
|
|
68
|
+
'medium': '\033[1;33m',
|
|
69
|
+
'low': '\033[1;34m',
|
|
70
|
+
'info': '\033[2;37m',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ── Data model ────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class Exploit:
|
|
78
|
+
"""Loaded exploit module with metadata."""
|
|
79
|
+
id: str
|
|
80
|
+
path: Path
|
|
81
|
+
metadata: Dict = field(default_factory=dict)
|
|
82
|
+
|
|
83
|
+
# Callable attributes set after load
|
|
84
|
+
_check_fn: Optional[Callable] = field(default=None, repr=False)
|
|
85
|
+
_run_fn: Optional[Callable] = field(default=None, repr=False)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def title(self) -> str:
|
|
89
|
+
return self.metadata.get('title', self.id)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def cve(self) -> str:
|
|
93
|
+
return self.metadata.get('cve', '')
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def severity(self) -> str:
|
|
97
|
+
return self.metadata.get('severity', 'info')
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def cvss(self) -> float:
|
|
101
|
+
try:
|
|
102
|
+
return float(self.metadata.get('cvss', 0))
|
|
103
|
+
except (TypeError, ValueError):
|
|
104
|
+
return 0.0
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def vendors(self) -> List[str]:
|
|
108
|
+
return self.metadata.get('vendor', [])
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def model_patterns(self) -> List[str]:
|
|
112
|
+
return self.metadata.get('model_patterns', [])
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def category(self) -> str:
|
|
116
|
+
return self.metadata.get('category', '')
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def protocol(self) -> str:
|
|
120
|
+
return self.metadata.get('protocol', '')
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def port(self) -> int:
|
|
124
|
+
return int(self.metadata.get('port', 0))
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def requires(self) -> List[str]:
|
|
128
|
+
return self.metadata.get('requires', [])
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def tags(self) -> List[str]:
|
|
132
|
+
return self.metadata.get('tags', [])
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def source_url(self) -> str:
|
|
136
|
+
return self.metadata.get('url', '')
|
|
137
|
+
|
|
138
|
+
def check(self, host: str, port: int = None,
|
|
139
|
+
timeout: float = 8) -> bool:
|
|
140
|
+
"""Run the non-destructive vulnerability check."""
|
|
141
|
+
if not self._check_fn:
|
|
142
|
+
return False
|
|
143
|
+
p = port or self.port
|
|
144
|
+
try:
|
|
145
|
+
return bool(self._check_fn(host, p, timeout))
|
|
146
|
+
except Exception as exc:
|
|
147
|
+
_log.debug("[%s] check() error: %s", self.id, exc)
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def run(self, host: str, port: int = None, timeout: float = 15,
|
|
151
|
+
dry_run: bool = True, **kwargs) -> Dict:
|
|
152
|
+
"""Execute the exploit."""
|
|
153
|
+
if not self._run_fn:
|
|
154
|
+
return {'success': False, 'error': 'exploit.py not loaded', 'evidence': ''}
|
|
155
|
+
p = port or self.port
|
|
156
|
+
try:
|
|
157
|
+
return self._run_fn(host, p, timeout, dry_run=dry_run, **kwargs)
|
|
158
|
+
except Exception as exc:
|
|
159
|
+
return {'success': False, 'error': str(exc)[:120], 'evidence': ''}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ── Loader ────────────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
def _load_metadata(directory: Path) -> Dict:
|
|
165
|
+
"""Load metadata.json from an exploit directory."""
|
|
166
|
+
meta_path = directory / 'metadata.json'
|
|
167
|
+
if meta_path.exists():
|
|
168
|
+
try:
|
|
169
|
+
with open(meta_path, encoding='utf-8') as f:
|
|
170
|
+
return json.load(f)
|
|
171
|
+
except Exception as exc:
|
|
172
|
+
_log.warning("metadata.json error in %s: %s", directory.name, exc)
|
|
173
|
+
return {}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _load_module(directory: Path) -> Tuple[Optional[Callable], Optional[Callable], Dict]:
|
|
177
|
+
"""
|
|
178
|
+
Dynamically import exploit.py from directory.
|
|
179
|
+
|
|
180
|
+
Returns: (check_fn, run_fn, embedded_metadata)
|
|
181
|
+
"""
|
|
182
|
+
py_path = directory / 'exploit.py'
|
|
183
|
+
if not py_path.exists():
|
|
184
|
+
return None, None, {}
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
spec = importlib.util.spec_from_file_location(
|
|
188
|
+
f'xpl_{directory.name}', py_path
|
|
189
|
+
)
|
|
190
|
+
mod = importlib.util.module_from_spec(spec)
|
|
191
|
+
spec.loader.exec_module(mod)
|
|
192
|
+
|
|
193
|
+
check_fn = getattr(mod, 'check', None)
|
|
194
|
+
run_fn = getattr(mod, 'run', None)
|
|
195
|
+
meta = getattr(mod, 'METADATA', {})
|
|
196
|
+
return check_fn, run_fn, meta
|
|
197
|
+
except Exception as exc:
|
|
198
|
+
_log.warning("Failed to load %s: %s", py_path, exc)
|
|
199
|
+
return None, None, {}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def load_all_exploits(source_filter: str = None) -> List[Exploit]:
|
|
203
|
+
"""
|
|
204
|
+
Discover and load all exploits from xpl/ directory tree.
|
|
205
|
+
|
|
206
|
+
Scans:
|
|
207
|
+
- Direct subdirectories of xpl/ (edb-15631/, etc.)
|
|
208
|
+
- xpl/custom/ — standalone .py exploits (user-created)
|
|
209
|
+
- xpl/msf/ — Metasploit-ported modules
|
|
210
|
+
- xpl/research/ — original research / PoC modules
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
source_filter: if set, only load exploits where metadata['source'] matches
|
|
214
|
+
one of: 'exploit-db', 'metasploit', 'research', 'custom'
|
|
215
|
+
|
|
216
|
+
Returns list of loaded Exploit instances, sorted by severity.
|
|
217
|
+
"""
|
|
218
|
+
if not XPL_DIR.exists():
|
|
219
|
+
_log.warning("xpl/ directory not found at %s", XPL_DIR)
|
|
220
|
+
return []
|
|
221
|
+
|
|
222
|
+
exploits: List[Exploit] = []
|
|
223
|
+
|
|
224
|
+
# ── Category subfolders (msf/, research/) ─────────────────────────────────
|
|
225
|
+
_CATEGORY_DIRS = {'msf', 'research'}
|
|
226
|
+
|
|
227
|
+
for item in sorted(XPL_DIR.iterdir()):
|
|
228
|
+
if not item.is_dir() or item.name.startswith('.'):
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
# standalone .py exploits (custom/)
|
|
232
|
+
if item.name == 'custom':
|
|
233
|
+
for sub in sorted(item.iterdir()):
|
|
234
|
+
if sub.is_file() and sub.suffix == '.py' and not sub.name.startswith('_'):
|
|
235
|
+
_load_single_py_exploit(sub, exploits)
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
# category subdirectory (msf/, research/) — recurse one level
|
|
239
|
+
if item.name in _CATEGORY_DIRS:
|
|
240
|
+
for sub_dir in sorted(item.iterdir()):
|
|
241
|
+
if not sub_dir.is_dir() or sub_dir.name.startswith('.'):
|
|
242
|
+
continue
|
|
243
|
+
_load_dir_exploit(sub_dir, exploits)
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
# Standard edb-XXXXX/ directory
|
|
247
|
+
_load_dir_exploit(item, exploits)
|
|
248
|
+
|
|
249
|
+
# Filter by source if requested
|
|
250
|
+
if source_filter:
|
|
251
|
+
sf = source_filter.lower()
|
|
252
|
+
exploits = [x for x in exploits
|
|
253
|
+
if x.metadata.get('source', '').lower() == sf]
|
|
254
|
+
|
|
255
|
+
return sorted(exploits, key=lambda x: _SEV_ORDER.get(x.severity, 99))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _load_dir_exploit(directory: Path, exploits: list) -> None:
|
|
259
|
+
"""Load a standard directory-based exploit (metadata.json + exploit.py)."""
|
|
260
|
+
meta_file = directory / 'metadata.json'
|
|
261
|
+
py_file = directory / 'exploit.py'
|
|
262
|
+
if not meta_file.exists() and not py_file.exists():
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
meta = _load_metadata(directory)
|
|
266
|
+
check_fn, run_fn, emb_meta = _load_module(directory)
|
|
267
|
+
merged_meta = {**meta, **emb_meta} if emb_meta else meta
|
|
268
|
+
|
|
269
|
+
exploit_id = merged_meta.get('id', directory.name.upper())
|
|
270
|
+
xpl = Exploit(
|
|
271
|
+
id=exploit_id,
|
|
272
|
+
path=directory,
|
|
273
|
+
metadata=merged_meta,
|
|
274
|
+
_check_fn=check_fn,
|
|
275
|
+
_run_fn=run_fn,
|
|
276
|
+
)
|
|
277
|
+
exploits.append(xpl)
|
|
278
|
+
_log.debug("Loaded exploit: %s", exploit_id)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _load_single_py_exploit(py_path: Path, exploits: list) -> None:
|
|
282
|
+
"""Load a standalone .py exploit file (custom/ style)."""
|
|
283
|
+
try:
|
|
284
|
+
spec = importlib.util.spec_from_file_location(f'xpl_custom_{py_path.stem}', py_path)
|
|
285
|
+
mod = importlib.util.module_from_spec(spec)
|
|
286
|
+
spec.loader.exec_module(mod)
|
|
287
|
+
|
|
288
|
+
meta = getattr(mod, 'METADATA', {})
|
|
289
|
+
check_fn = getattr(mod, 'check', None)
|
|
290
|
+
run_fn = getattr(mod, 'run', None)
|
|
291
|
+
|
|
292
|
+
if not meta and not check_fn:
|
|
293
|
+
return # Skip files without the required interface
|
|
294
|
+
|
|
295
|
+
exploit_id = meta.get('id', py_path.stem.upper())
|
|
296
|
+
xpl = Exploit(
|
|
297
|
+
id=exploit_id,
|
|
298
|
+
path=py_path.parent,
|
|
299
|
+
metadata=meta,
|
|
300
|
+
_check_fn=check_fn,
|
|
301
|
+
_run_fn=run_fn,
|
|
302
|
+
)
|
|
303
|
+
exploits.append(xpl)
|
|
304
|
+
except Exception as exc:
|
|
305
|
+
_log.debug("Custom exploit load error %s: %s", py_path.name, exc)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# ── Matcher ───────────────────────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
def match_exploits(
|
|
311
|
+
exploits: List[Exploit],
|
|
312
|
+
make: str = '',
|
|
313
|
+
model: str = '',
|
|
314
|
+
firmware: str = '',
|
|
315
|
+
open_ports: List[int] = None,
|
|
316
|
+
langs: List[str] = None,
|
|
317
|
+
cves: List[str] = None,
|
|
318
|
+
) -> List[Exploit]:
|
|
319
|
+
"""
|
|
320
|
+
Filter exploits matching a specific printer fingerprint.
|
|
321
|
+
|
|
322
|
+
Matching criteria (ANY match = included):
|
|
323
|
+
- CVE match against known CVE list
|
|
324
|
+
- Vendor/make name match (case-insensitive)
|
|
325
|
+
- Model pattern regex match
|
|
326
|
+
- Port requirement satisfied
|
|
327
|
+
- Language requirement satisfied
|
|
328
|
+
- Firmware pattern match
|
|
329
|
+
|
|
330
|
+
Returns sorted list of matched exploits (critical first).
|
|
331
|
+
"""
|
|
332
|
+
ports = set(open_ports or [])
|
|
333
|
+
langs_upper = {l.upper() for l in (langs or [])}
|
|
334
|
+
cves_upper = {c.upper() for c in (cves or [])}
|
|
335
|
+
target_str = f"{make} {model} {firmware}".lower()
|
|
336
|
+
|
|
337
|
+
matched = []
|
|
338
|
+
for xpl in exploits:
|
|
339
|
+
score = 0
|
|
340
|
+
|
|
341
|
+
# CVE match
|
|
342
|
+
if xpl.cve and xpl.cve.upper() in cves_upper:
|
|
343
|
+
score += 10
|
|
344
|
+
|
|
345
|
+
# Vendor match
|
|
346
|
+
vendors_lower = [v.lower() for v in xpl.vendors]
|
|
347
|
+
if make.lower() and any(v in make.lower() or make.lower() in v
|
|
348
|
+
for v in vendors_lower if v):
|
|
349
|
+
score += 5
|
|
350
|
+
|
|
351
|
+
# Model pattern match
|
|
352
|
+
for pat in xpl.model_patterns:
|
|
353
|
+
if pat and re.search(pat, target_str, re.I):
|
|
354
|
+
score += 3
|
|
355
|
+
break
|
|
356
|
+
|
|
357
|
+
# Firmware pattern match
|
|
358
|
+
for pat in xpl.metadata.get('firmware_patterns', []):
|
|
359
|
+
if pat and re.search(pat, firmware, re.I):
|
|
360
|
+
score += 2
|
|
361
|
+
break
|
|
362
|
+
|
|
363
|
+
# Port requirement match
|
|
364
|
+
req_ports_met = True
|
|
365
|
+
for req in xpl.requires:
|
|
366
|
+
if req.startswith('port:'):
|
|
367
|
+
p = int(req.split(':')[1])
|
|
368
|
+
if p not in ports:
|
|
369
|
+
req_ports_met = False
|
|
370
|
+
break
|
|
371
|
+
elif req.startswith('lang:'):
|
|
372
|
+
lang = req.split(':')[1].upper()
|
|
373
|
+
if lang not in langs_upper:
|
|
374
|
+
score = max(score - 1, 0) # penalise but don't exclude
|
|
375
|
+
|
|
376
|
+
if not req_ports_met:
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
# If vendors specified but none match, still include if port matches
|
|
380
|
+
if xpl.vendors and score == 0 and ports & {xpl.port}:
|
|
381
|
+
score = 1 # low-confidence match
|
|
382
|
+
|
|
383
|
+
# No vendor specified = generic (matches anything with right port)
|
|
384
|
+
if not xpl.vendors and ports & {xpl.port}:
|
|
385
|
+
score = max(score, 1)
|
|
386
|
+
|
|
387
|
+
if score > 0:
|
|
388
|
+
matched.append((score, xpl))
|
|
389
|
+
|
|
390
|
+
# Sort: higher score first, then severity
|
|
391
|
+
matched.sort(key=lambda x: (-x[0], _SEV_ORDER.get(x[1].severity, 99)))
|
|
392
|
+
return [x[1] for x in matched]
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
# ── Printer ───────────────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
_SRC_BADGE = {
|
|
398
|
+
'metasploit': '\033[1;35m[MSF]\033[0m',
|
|
399
|
+
'exploit-db': '\033[1;34m[EDB]\033[0m',
|
|
400
|
+
'research': '\033[1;33m[RES]\033[0m',
|
|
401
|
+
'custom': '\033[1;32m[USR]\033[0m',
|
|
402
|
+
'nvd': '\033[1;36m[NVD]\033[0m',
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def print_exploit_list(exploits: List[Exploit], title: str = 'Available Exploits') -> None:
|
|
407
|
+
"""Pretty-print a list of exploits to stdout."""
|
|
408
|
+
if not exploits:
|
|
409
|
+
print(f" {_DIM}No exploits found.{_RST}")
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
# Group by source for summary
|
|
413
|
+
by_source: Dict[str, int] = {}
|
|
414
|
+
for x in exploits:
|
|
415
|
+
s = x.metadata.get('source', 'unknown')
|
|
416
|
+
by_source[s] = by_source.get(s, 0) + 1
|
|
417
|
+
|
|
418
|
+
print(f"\n {_CYN}{'='*72}{_RST}")
|
|
419
|
+
print(f" {_CYN}{title} ({len(exploits)}){_RST}")
|
|
420
|
+
src_summary = ' '.join(
|
|
421
|
+
f"{_SRC_BADGE.get(s, f'[{s}]')} {n}"
|
|
422
|
+
for s, n in sorted(by_source.items())
|
|
423
|
+
)
|
|
424
|
+
print(f" {src_summary}")
|
|
425
|
+
print(f" {_CYN}{'='*72}{_RST}")
|
|
426
|
+
print(f" {'SRC':<6} {'ID':<30} {'SEV':<10} {'CVSS':<6} {'CAT':<16} TITLE")
|
|
427
|
+
print(f" {'-'*72}")
|
|
428
|
+
for xpl in exploits:
|
|
429
|
+
sev_clr = _SEV_CLR.get(xpl.severity, _DIM)
|
|
430
|
+
cve_str = f"[{xpl.cve}]" if xpl.cve else ''
|
|
431
|
+
src = xpl.metadata.get('source', '?')
|
|
432
|
+
badge = _SRC_BADGE.get(src, f'[{src[:3].upper()}]')
|
|
433
|
+
print(f" {badge} {_GRN}{xpl.id:<30}{_RST} "
|
|
434
|
+
f"{sev_clr}{xpl.severity:<10}{_RST} "
|
|
435
|
+
f"{xpl.cvss:<6.1f} "
|
|
436
|
+
f"{xpl.category:<16} "
|
|
437
|
+
f"{xpl.title[:35]}")
|
|
438
|
+
if cve_str:
|
|
439
|
+
print(f" {'':6} {'':30} {_DIM}{cve_str}{_RST}")
|
|
440
|
+
print()
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def print_matched_exploits(matched: List[Exploit], target: str) -> None:
|
|
444
|
+
"""Print matched exploits for a scanned target (compact format for --scan output)."""
|
|
445
|
+
if not matched:
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
print(f"\n {_RED}{'!'*3} EXPLOITS AVAILABLE FOR {target} {'!'*3}{_RST}")
|
|
449
|
+
print(f" {'-'*65}")
|
|
450
|
+
for xpl in matched[:10]:
|
|
451
|
+
sev_clr = _SEV_CLR.get(xpl.severity, _DIM)
|
|
452
|
+
cve_str = f" CVE: {xpl.cve}" if xpl.cve else ''
|
|
453
|
+
ref_str = f" {_DIM}{xpl.source_url[:55]}{_RST}" if xpl.source_url else ''
|
|
454
|
+
print(f" {sev_clr}[{xpl.severity.upper()}]{_RST} {_GRN}{xpl.id}{_RST} {xpl.title}")
|
|
455
|
+
if cve_str:
|
|
456
|
+
print(f" {_DIM}{cve_str}{_RST}")
|
|
457
|
+
if ref_str:
|
|
458
|
+
print(ref_str)
|
|
459
|
+
print(f" {_DIM}Run: python src/main.py {target} --xpl-run {xpl.id} --dry{_RST}")
|
|
460
|
+
print()
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def print_run_result(result: Dict, exploit_id: str) -> None:
|
|
464
|
+
"""Print the result of running an exploit."""
|
|
465
|
+
success = result.get('success', False)
|
|
466
|
+
evidence = result.get('evidence', '')
|
|
467
|
+
error = result.get('error', '')
|
|
468
|
+
creds = result.get('credentials', [])
|
|
469
|
+
files = result.get('files', [])
|
|
470
|
+
vuln = result.get('vulnerable', success)
|
|
471
|
+
|
|
472
|
+
icon = f"{_RED}[EXPLOITED]{_RST}" if (success and not result.get('dry_run')) else (
|
|
473
|
+
f"{_YEL}[VULN]{_RST}" if vuln else
|
|
474
|
+
f"{_GRN}[OK / NOT VULN]{_RST}")
|
|
475
|
+
|
|
476
|
+
print(f"\n {icon} {exploit_id}")
|
|
477
|
+
if evidence:
|
|
478
|
+
print(f" Evidence:")
|
|
479
|
+
for line in evidence.strip().splitlines():
|
|
480
|
+
print(f" {line}")
|
|
481
|
+
if creds:
|
|
482
|
+
print(f"\n {_RED}Credentials / Sensitive Data:{_RST}")
|
|
483
|
+
for c in creds[:10]:
|
|
484
|
+
print(f" {c}")
|
|
485
|
+
if files:
|
|
486
|
+
print(f"\n Files ({len(files)}):")
|
|
487
|
+
for f in files[:15]:
|
|
488
|
+
print(f" {f.get('type','?'):5} {f.get('path','?')}")
|
|
489
|
+
if error:
|
|
490
|
+
print(f" {_DIM}Error: {error}{_RST}")
|
|
491
|
+
print()
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
# ── Update / download ─────────────────────────────────────────────────────────
|
|
495
|
+
|
|
496
|
+
def update_index(exploits: List[Exploit]) -> None:
|
|
497
|
+
"""Regenerate xpl/index.json from loaded exploits."""
|
|
498
|
+
index = {
|
|
499
|
+
'_comment': 'PrinterXPL-Forge exploit index. Auto-regenerated by --xpl-update.',
|
|
500
|
+
'_version': '2.0',
|
|
501
|
+
'_generated': time.strftime('%Y-%m-%d'),
|
|
502
|
+
'exploits': [
|
|
503
|
+
{
|
|
504
|
+
'id': x.id,
|
|
505
|
+
'source': x.metadata.get('source', 'unknown'),
|
|
506
|
+
'path': str(x.path.relative_to(XPL_DIR)),
|
|
507
|
+
'cve': x.cve,
|
|
508
|
+
'title': x.title,
|
|
509
|
+
'severity': x.severity,
|
|
510
|
+
'vendor': x.vendors,
|
|
511
|
+
'category': x.category,
|
|
512
|
+
'port': x.port,
|
|
513
|
+
'protocol': x.protocol,
|
|
514
|
+
'cvss': x.cvss,
|
|
515
|
+
'url': x.source_url,
|
|
516
|
+
}
|
|
517
|
+
for x in exploits
|
|
518
|
+
],
|
|
519
|
+
}
|
|
520
|
+
with open(INDEX_FILE, 'w', encoding='utf-8') as f:
|
|
521
|
+
json.dump(index, f, indent=2)
|
|
522
|
+
# Print breakdown by source
|
|
523
|
+
by_src: Dict[str, int] = {}
|
|
524
|
+
for x in exploits:
|
|
525
|
+
s = x.metadata.get('source', 'unknown')
|
|
526
|
+
by_src[s] = by_src.get(s, 0) + 1
|
|
527
|
+
src_str = ', '.join(f'{s}:{n}' for s, n in sorted(by_src.items()))
|
|
528
|
+
print(f" {_GRN}[+]{_RST} index.json updated — {len(exploits)} exploits [{src_str}]")
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def fetch_exploit_db_raw(edb_id: str, output_dir: Path = None) -> Optional[Path]:
|
|
532
|
+
"""
|
|
533
|
+
Download a raw exploit from ExploitDB by ID.
|
|
534
|
+
|
|
535
|
+
Creates xpl/edb-{id}/ directory and saves the raw file.
|
|
536
|
+
Returns the path to the saved file, or None on failure.
|
|
537
|
+
"""
|
|
538
|
+
try:
|
|
539
|
+
import requests
|
|
540
|
+
import urllib3
|
|
541
|
+
urllib3.disable_warnings()
|
|
542
|
+
|
|
543
|
+
url = f"https://www.exploit-db.com/raw/{edb_id}"
|
|
544
|
+
r = requests.get(url, timeout=20, verify=False,
|
|
545
|
+
headers={'User-Agent': 'PrinterXPL-Forge/3.15'})
|
|
546
|
+
if r.status_code != 200:
|
|
547
|
+
print(f" {_YEL}[!]{_RST} EDB-{edb_id}: HTTP {r.status_code}")
|
|
548
|
+
return None
|
|
549
|
+
|
|
550
|
+
save_dir = output_dir or (XPL_DIR / f'edb-{edb_id}')
|
|
551
|
+
save_dir.mkdir(parents=True, exist_ok=True)
|
|
552
|
+
|
|
553
|
+
# Determine extension from content
|
|
554
|
+
content = r.text
|
|
555
|
+
ext = '.rb' if 'Msf::' in content else '.py' if 'import ' in content else '.txt'
|
|
556
|
+
out = save_dir / f'raw_edb-{edb_id}{ext}'
|
|
557
|
+
out.write_text(content, encoding='utf-8')
|
|
558
|
+
|
|
559
|
+
print(f" {_GRN}[+]{_RST} EDB-{edb_id}: saved to {out}")
|
|
560
|
+
return out
|
|
561
|
+
except Exception as exc:
|
|
562
|
+
print(f" {_YEL}[!]{_RST} EDB-{edb_id}: download failed — {exc}")
|
|
563
|
+
return None
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
# ── Convenience API ───────────────────────────────────────────────────────────
|
|
567
|
+
|
|
568
|
+
def get_matched_for_target(
|
|
569
|
+
make: str,
|
|
570
|
+
model: str,
|
|
571
|
+
firmware: str = '',
|
|
572
|
+
open_ports: List[int] = None,
|
|
573
|
+
langs: List[str] = None,
|
|
574
|
+
cves: List[str] = None,
|
|
575
|
+
source_filter: str = None,
|
|
576
|
+
) -> List[Exploit]:
|
|
577
|
+
"""
|
|
578
|
+
One-shot: load all exploits and return those matching the target.
|
|
579
|
+
|
|
580
|
+
Called from banner_grabber / vuln_scanner / scan pipeline.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
source_filter: optionally restrict to 'metasploit', 'exploit-db',
|
|
584
|
+
'research', or 'custom'.
|
|
585
|
+
"""
|
|
586
|
+
all_exploits = load_all_exploits(source_filter=source_filter)
|
|
587
|
+
return match_exploits(
|
|
588
|
+
all_exploits,
|
|
589
|
+
make=make, model=model, firmware=firmware,
|
|
590
|
+
open_ports=open_ports, langs=langs, cves=cves,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def list_by_source(source: str) -> List[Exploit]:
|
|
595
|
+
"""Return all exploits matching a specific source label."""
|
|
596
|
+
return load_all_exploits(source_filter=source)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
# ── Auto Exploit ───────────────────────────────────────────────────────────────
|
|
600
|
+
|
|
601
|
+
from dataclasses import dataclass as _dc, field as _field
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
@_dc
|
|
605
|
+
class AutoExploitResult:
|
|
606
|
+
"""Result of an auto_exploit() run for a single exploit module."""
|
|
607
|
+
exploit: Exploit
|
|
608
|
+
vulnerable: bool
|
|
609
|
+
ran: bool = False
|
|
610
|
+
result: Dict = _field(default_factory=dict)
|
|
611
|
+
params: Dict = _field(default_factory=dict) # pre-filled params used
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def auto_exploit(
|
|
615
|
+
host: str,
|
|
616
|
+
*,
|
|
617
|
+
make: str = '',
|
|
618
|
+
model: str = '',
|
|
619
|
+
firmware: str = '',
|
|
620
|
+
open_ports: List[int] = None,
|
|
621
|
+
langs: List[str] = None,
|
|
622
|
+
cves: List[str] = None,
|
|
623
|
+
serial: str = '',
|
|
624
|
+
mac: str = '',
|
|
625
|
+
source_filter: str = None,
|
|
626
|
+
custom_xpl_path: str = None,
|
|
627
|
+
dry_run: bool = True,
|
|
628
|
+
check_limit: int = 8,
|
|
629
|
+
run_top_n: int = 1,
|
|
630
|
+
timeout: float = 10.0,
|
|
631
|
+
verbose: bool = True,
|
|
632
|
+
) -> List[AutoExploitResult]:
|
|
633
|
+
"""
|
|
634
|
+
Automatic exploit selection, verification, and execution.
|
|
635
|
+
|
|
636
|
+
Algorithm:
|
|
637
|
+
1. Load all available exploits (or from custom_xpl_path if provided).
|
|
638
|
+
2. Match against the target fingerprint (make/model/firmware/ports/cves).
|
|
639
|
+
3. Sort candidates by CVSS score descending.
|
|
640
|
+
4. Run check() on the top `check_limit` candidates (non-destructive).
|
|
641
|
+
5. For confirmed-vulnerable exploits, pre-fill all required parameters
|
|
642
|
+
(host, port, serial, mac, vendor) automatically.
|
|
643
|
+
6. Execute run() on the top `run_top_n` confirmed exploits (dry-run by default).
|
|
644
|
+
7. Return ordered list of AutoExploitResult.
|
|
645
|
+
|
|
646
|
+
Args:
|
|
647
|
+
host: Target IP or hostname.
|
|
648
|
+
make: Vendor name from fingerprint (e.g. 'Epson').
|
|
649
|
+
model: Model string from fingerprint (e.g. 'L3250 Series').
|
|
650
|
+
firmware: Firmware version string.
|
|
651
|
+
open_ports: List of open ports detected during scan.
|
|
652
|
+
langs: Printer languages supported (e.g. ['PJL', 'PS']).
|
|
653
|
+
cves: Known CVEs from NVD scan.
|
|
654
|
+
serial: Device serial number (from --bf-serial or scan).
|
|
655
|
+
mac: Device MAC address (from --bf-mac or scan).
|
|
656
|
+
source_filter: Restrict to 'exploit-db', 'metasploit', 'research', 'custom'.
|
|
657
|
+
custom_xpl_path: Path to a custom exploit .py file to force-use.
|
|
658
|
+
dry_run: If True, run() is called in dry-run mode (no destructive actions).
|
|
659
|
+
check_limit: Maximum number of candidates to probe with check().
|
|
660
|
+
run_top_n: Execute run() on this many top confirmed-vulnerable exploits.
|
|
661
|
+
timeout: Socket timeout for check/run calls.
|
|
662
|
+
verbose: Print progress to stdout.
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
List of AutoExploitResult, ordered by CVSS descending.
|
|
666
|
+
Only entries where vulnerable=True and ran=True have result populated.
|
|
667
|
+
"""
|
|
668
|
+
results: List[AutoExploitResult] = []
|
|
669
|
+
|
|
670
|
+
# ── Step 1 & 2: load and match ──────────────────────────────────────────
|
|
671
|
+
if custom_xpl_path:
|
|
672
|
+
from pathlib import Path as _Path
|
|
673
|
+
candidates: List[Exploit] = []
|
|
674
|
+
_load_single_py_exploit(_Path(custom_xpl_path), candidates)
|
|
675
|
+
if not candidates:
|
|
676
|
+
if verbose:
|
|
677
|
+
print(f" {_YEL}[!]{_RST} Could not load custom exploit from {custom_xpl_path}")
|
|
678
|
+
return []
|
|
679
|
+
else:
|
|
680
|
+
all_xpls = load_all_exploits(source_filter=source_filter)
|
|
681
|
+
candidates = match_exploits(
|
|
682
|
+
all_xpls,
|
|
683
|
+
make=make, model=model, firmware=firmware,
|
|
684
|
+
open_ports=open_ports, langs=langs, cves=cves,
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
if not candidates:
|
|
688
|
+
if verbose:
|
|
689
|
+
print(f" {_DIM}[auto-exploit] No matching exploits found for {make} {model}{_RST}")
|
|
690
|
+
return []
|
|
691
|
+
|
|
692
|
+
if verbose:
|
|
693
|
+
print(f"\n {_CYN}{'='*65}{_RST}")
|
|
694
|
+
print(f" {_CYN}AUTO EXPLOIT — {host}{_RST}")
|
|
695
|
+
print(f" Target : {make} {model} {firmware}".strip())
|
|
696
|
+
print(f" Matched : {len(candidates)} exploit(s)")
|
|
697
|
+
print(f" Mode : {'DRY-RUN' if dry_run else 'LIVE'}")
|
|
698
|
+
print(f" {_CYN}{'='*65}{_RST}")
|
|
699
|
+
|
|
700
|
+
# ── Step 3: sort by CVSS ────────────────────────────────────────────────
|
|
701
|
+
candidates.sort(key=lambda x: (-x.cvss, _SEV_ORDER.get(x.severity, 99)))
|
|
702
|
+
|
|
703
|
+
# ── Step 4: check() — verify vulnerability ──────────────────────────────
|
|
704
|
+
confirmed: List[Exploit] = []
|
|
705
|
+
probed = 0
|
|
706
|
+
for xpl in candidates:
|
|
707
|
+
if probed >= check_limit:
|
|
708
|
+
break
|
|
709
|
+
probed += 1
|
|
710
|
+
|
|
711
|
+
port = xpl.port or (open_ports[0] if open_ports else 9100)
|
|
712
|
+
sev_clr = _SEV_CLR.get(xpl.severity, _DIM)
|
|
713
|
+
|
|
714
|
+
if verbose:
|
|
715
|
+
print(f" {_DIM}[check]{_RST} {sev_clr}{xpl.severity.upper():<8}{_RST} "
|
|
716
|
+
f"CVSS {xpl.cvss:<5.1f} {_GRN}{xpl.id}{_RST} {xpl.title[:40]}")
|
|
717
|
+
|
|
718
|
+
vuln = xpl.check(host, port=port, timeout=timeout)
|
|
719
|
+
|
|
720
|
+
result_entry = AutoExploitResult(
|
|
721
|
+
exploit = xpl,
|
|
722
|
+
vulnerable = vuln,
|
|
723
|
+
params = _prefill_params(xpl, host, port, serial, mac, make),
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
if verbose:
|
|
727
|
+
icon = f"{_RED}[VULN]{_RST}" if vuln else f"{_GRN}[NOT VULN]{_RST}"
|
|
728
|
+
print(f" {icon}")
|
|
729
|
+
|
|
730
|
+
results.append(result_entry)
|
|
731
|
+
if vuln:
|
|
732
|
+
confirmed.append(xpl)
|
|
733
|
+
|
|
734
|
+
# ── Step 5 & 6: run() on top confirmed ──────────────────────────────────
|
|
735
|
+
run_count = 0
|
|
736
|
+
for res in results:
|
|
737
|
+
if not res.vulnerable:
|
|
738
|
+
continue
|
|
739
|
+
if run_count >= run_top_n:
|
|
740
|
+
break
|
|
741
|
+
run_count += 1
|
|
742
|
+
|
|
743
|
+
xpl = res.exploit
|
|
744
|
+
port = res.params.get('port', xpl.port or 9100)
|
|
745
|
+
extra = {k: v for k, v in res.params.items() if k not in ('host', 'port')}
|
|
746
|
+
|
|
747
|
+
if verbose:
|
|
748
|
+
mode_str = f"{_YEL}DRY-RUN{_RST}" if dry_run else f"{_RED}LIVE{_RST}"
|
|
749
|
+
print(f"\n {mode_str} Running {_GRN}{xpl.id}{_RST} against {host}:{port}")
|
|
750
|
+
|
|
751
|
+
run_result = xpl.run(host, port=port, timeout=timeout, dry_run=dry_run, **extra)
|
|
752
|
+
res.ran = True
|
|
753
|
+
res.result = run_result
|
|
754
|
+
print_run_result(run_result, xpl.id)
|
|
755
|
+
|
|
756
|
+
if verbose and not any(r.vulnerable for r in results):
|
|
757
|
+
print(f"\n {_GRN}[OK]{_RST} No confirmed vulnerabilities in the top {probed} checked exploits.")
|
|
758
|
+
|
|
759
|
+
return results
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _prefill_params(xpl: Exploit, host: str, port: int,
|
|
763
|
+
serial: str, mac: str, make: str) -> Dict:
|
|
764
|
+
"""
|
|
765
|
+
Build the kwargs dict to pass to exploit run(), pre-filling known values.
|
|
766
|
+
|
|
767
|
+
Maps standard exploit.requires keys (serial, mac, vendor) to values
|
|
768
|
+
extracted from fingerprint / CLI arguments.
|
|
769
|
+
"""
|
|
770
|
+
params: Dict = {
|
|
771
|
+
'host': host,
|
|
772
|
+
'port': port or xpl.port or 9100,
|
|
773
|
+
}
|
|
774
|
+
requires = xpl.requires or []
|
|
775
|
+
for req in requires:
|
|
776
|
+
if req == 'serial' and serial:
|
|
777
|
+
params['serial'] = serial
|
|
778
|
+
elif req == 'mac' and mac:
|
|
779
|
+
params['mac'] = mac
|
|
780
|
+
elif req == 'vendor' and make:
|
|
781
|
+
params['vendor'] = make.lower()
|
|
782
|
+
return params
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def print_auto_exploit_summary(results: List[AutoExploitResult]) -> None:
|
|
786
|
+
"""Print a summary table of all auto-exploit results."""
|
|
787
|
+
if not results:
|
|
788
|
+
return
|
|
789
|
+
vuln_count = sum(1 for r in results if r.vulnerable)
|
|
790
|
+
ran_count = sum(1 for r in results if r.ran)
|
|
791
|
+
|
|
792
|
+
print(f"\n {_CYN}{'='*65}{_RST}")
|
|
793
|
+
print(f" {_CYN}AUTO EXPLOIT SUMMARY{_RST}")
|
|
794
|
+
print(f" Checked : {len(results)} exploit(s)")
|
|
795
|
+
print(f" Vulnerable : {_RED if vuln_count else _GRN}{vuln_count}{_RST}")
|
|
796
|
+
print(f" Executed : {ran_count}")
|
|
797
|
+
print(f" {_CYN}{'='*65}{_RST}")
|
|
798
|
+
print(f" {'EXPLOIT':<30} {'CVSS':<6} {'VULN':<8} {'RAN':<5} STATUS")
|
|
799
|
+
print(f" {'-'*65}")
|
|
800
|
+
for r in sorted(results, key=lambda x: -x.exploit.cvss):
|
|
801
|
+
vuln_icon = f"{_RED}YES{_RST}" if r.vulnerable else f"{_GRN}no{_RST}"
|
|
802
|
+
ran_icon = "YES" if r.ran else "no"
|
|
803
|
+
status = r.result.get('output', '')[:30] if r.result else ''
|
|
804
|
+
print(f" {r.exploit.id:<30} {r.exploit.cvss:<6.1f} {vuln_icon:<8} {ran_icon:<5} {status}")
|
|
805
|
+
print()
|