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.
Files changed (97) hide show
  1. nse/README.md +204 -0
  2. nse/__init__.py +6 -0
  3. nse/install_nse.py +412 -0
  4. nse/lib/printerxpl.lua +238 -0
  5. nse/scripts/cups-info.nse +74 -0
  6. nse/scripts/cups-queue-info.nse +43 -0
  7. nse/scripts/hp-printers-cve-2022-1026.nse +121 -0
  8. nse/scripts/http-device-mac.nse +107 -0
  9. nse/scripts/http-hp-ilo-info.nse +121 -0
  10. nse/scripts/http-info-xerox-enum.nse +101 -0
  11. nse/scripts/http-vuln-cve2022-1026.nse +158 -0
  12. nse/scripts/lexmark-config.nse +89 -0
  13. nse/scripts/pjl-ready-message.nse +106 -0
  14. nse/scripts/printer-banner.nse +217 -0
  15. nse/scripts/printer-cups-rce.nse +189 -0
  16. nse/scripts/printer-cve-detect.nse +279 -0
  17. nse/scripts/printer-discover.nse +205 -0
  18. nse/scripts/printer-firmware-exposed.nse +219 -0
  19. nse/scripts/printer-hp-pjl.nse +192 -0
  20. nse/scripts/printer-http-ews.nse +293 -0
  21. nse/scripts/printer-ipp-info.nse +235 -0
  22. nse/scripts/printer-lexmark-ipp.nse +203 -0
  23. nse/scripts/printer-passback.nse +204 -0
  24. nse/scripts/printer-pjl-info.nse +146 -0
  25. nse/scripts/printer-printnightmare.nse +211 -0
  26. nse/scripts/printer-snmp-info.nse +176 -0
  27. nse/scripts/printer-vuln-check.nse +256 -0
  28. nse/scripts/snmp-device-mac.nse +93 -0
  29. nse/scripts/snmp-info.nse +146 -0
  30. nse/scripts/snmp-sysdescr.nse +70 -0
  31. printerxpl_forge-6.2.0.dist-info/METADATA +919 -0
  32. printerxpl_forge-6.2.0.dist-info/RECORD +97 -0
  33. printerxpl_forge-6.2.0.dist-info/WHEEL +5 -0
  34. printerxpl_forge-6.2.0.dist-info/entry_points.txt +4 -0
  35. printerxpl_forge-6.2.0.dist-info/licenses/LICENSE +21 -0
  36. printerxpl_forge-6.2.0.dist-info/top_level.txt +4 -0
  37. src/assets/fonts/gunplay.pfa +1671 -0
  38. src/assets/fonts/kshandwrt.pfa +315 -0
  39. src/assets/fonts/laksoner.pfa +2402 -0
  40. src/assets/fonts/paintcans.pfa +9699 -0
  41. src/assets/fonts/stencilod.pfa +4076 -0
  42. src/assets/fonts/takecover.pfa +26138 -0
  43. src/assets/fonts/topsecret.pfa +6652 -0
  44. src/assets/fonts/whoa.pfa +773 -0
  45. src/assets/mibs/HOST-RESOURCES-MIB +1540 -0
  46. src/assets/mibs/Printer-MIB +4389 -0
  47. src/assets/mibs/README.md +9 -0
  48. src/assets/mibs/SNMPv2-MIB +854 -0
  49. src/assets/overlays/hacker.eps +596 -0
  50. src/assets/overlays/smiley.eps +214 -0
  51. src/assets/overlays/smiley2.eps +240 -0
  52. src/core/attack_orchestrator.py +1025 -0
  53. src/core/capabilities.py +323 -0
  54. src/core/destructive_audit.py +430 -0
  55. src/core/discovery.py +488 -0
  56. src/core/osdetect.py +74 -0
  57. src/core/poly_runner.py +579 -0
  58. src/core/printer.py +1426 -0
  59. src/main.py +2134 -0
  60. src/modules/install_printer.py +318 -0
  61. src/modules/login_bruteforce.py +852 -0
  62. src/modules/pcl.py +506 -0
  63. src/modules/pjl.py +3575 -0
  64. src/modules/print_job.py +1290 -0
  65. src/modules/ps.py +1102 -0
  66. src/payloads/__init__.py +98 -0
  67. src/payloads/assets/overlays/notice.eps +9 -0
  68. src/protocols/__init__.py +19 -0
  69. src/protocols/firmware.py +738 -0
  70. src/protocols/ipp.py +216 -0
  71. src/protocols/ipp_attacks.py +609 -0
  72. src/protocols/lpd.py +141 -0
  73. src/protocols/network_map.py +1004 -0
  74. src/protocols/raw.py +173 -0
  75. src/protocols/smb.py +359 -0
  76. src/protocols/ssrf_pivot.py +427 -0
  77. src/protocols/storage.py +587 -0
  78. src/ui/__init__.py +6 -0
  79. src/ui/interactive.py +742 -0
  80. src/ui/spinner.py +112 -0
  81. src/ui/tables.py +132 -0
  82. src/utils/banner_grabber.py +852 -0
  83. src/utils/codebook.py +456 -0
  84. src/utils/config.py +522 -0
  85. src/utils/cve_loader.py +158 -0
  86. src/utils/default_creds.py +134 -0
  87. src/utils/discovery_online.py +1327 -0
  88. src/utils/exploit_manager.py +805 -0
  89. src/utils/fuzzer.py +220 -0
  90. src/utils/helper.py +732 -0
  91. src/utils/local_printers.py +307 -0
  92. src/utils/ml_engine.py +491 -0
  93. src/utils/operators.py +474 -0
  94. src/utils/ports.py +234 -0
  95. src/utils/vuln_scanner.py +823 -0
  96. src/utils/wordlist_loader.py +412 -0
  97. src/version.py +36 -0
@@ -0,0 +1,412 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ PrinterXPL-Forge — Wordlist Loader
5
+ ================================
6
+ Loads printer credential wordlists from external files.
7
+ No credentials are hardcoded in the Python source.
8
+
9
+ Wordlist format (user:pass per line):
10
+ - Lines starting with # are comments
11
+ - Lines matching `# ── VENDOR NAME ──` start a new vendor section
12
+ - Empty password: `admin:` or `admin:` (trailing colon = blank)
13
+ - Empty username: `:password`
14
+ - Both empty: `:`
15
+ - Token references: `admin:__SERIAL__`, `admin:__MAC6__`, `admin:__MAC12__`
16
+
17
+ Vendor section parsing example:
18
+ # ── HP (Hewlett-Packard) ─────────
19
+ admin:
20
+ admin:admin
21
+
22
+ Priority:
23
+ 1. Custom wordlist (--bf-wordlist)
24
+ 2. Default wordlist: wordlists/printer_default_creds.txt (auto-located)
25
+ 3. Vendor-specific fallback if a line belongs to the detected vendor section
26
+ 4. UNIVERSAL / GENERIC entries always appended
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 logging
36
+ import os
37
+ import re
38
+ from pathlib import Path
39
+ from typing import Dict, List, Optional, Set, Tuple
40
+
41
+ from utils.default_creds import Cred, SERIAL_TOKEN, MAC6_TOKEN, MAC12_TOKEN, _ALIASES
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+ # ── Vendor section header regex ───────────────────────────────────────────────
46
+ # Matches: # ── HP (Hewlett-Packard) ───────────────────────────────────────────
47
+ _SECTION_RE = re.compile(r'^#\s*[-─]+\s*(.+?)\s*[-─]*\s*$')
48
+
49
+ # Sections treated as "generic" / always included
50
+ _GENERIC_SECTIONS = {
51
+ 'universal', 'generic', 'universal / generic', 'common', 'general',
52
+ 'common default serial number patterns', 'common default', '',
53
+ }
54
+
55
+ # ── Wordlist discovery ────────────────────────────────────────────────────────
56
+
57
+ def _find_default_wordlist() -> Optional[Path]:
58
+ """
59
+ Locate the default printer_default_creds.txt wordlist.
60
+
61
+ Searches (in order):
62
+ 1. wordlists/ relative to this file's location (src/utils/../..)
63
+ 2. wordlists/ relative to cwd
64
+ 3. ~/.PrinterXPL-Forge/wordlists/
65
+ """
66
+ candidates = [
67
+ Path(__file__).parent.parent.parent / "wordlists" / "printer_default_creds.txt",
68
+ Path.cwd() / "wordlists" / "printer_default_creds.txt",
69
+ Path.home() / ".PrinterXPL-Forge" / "wordlists" / "printer_default_creds.txt",
70
+ ]
71
+ for p in candidates:
72
+ if p.exists():
73
+ return p
74
+ return None
75
+
76
+
77
+ def _find_snmp_wordlist() -> Optional[Path]:
78
+ """Locate snmp_communities.txt wordlist."""
79
+ candidates = [
80
+ Path(__file__).parent.parent.parent / "wordlists" / "snmp_communities.txt",
81
+ Path.cwd() / "wordlists" / "snmp_communities.txt",
82
+ ]
83
+ for p in candidates:
84
+ if p.exists():
85
+ return p
86
+ return None
87
+
88
+
89
+ def _find_ftp_wordlist() -> Optional[Path]:
90
+ """Locate ftp_creds.txt wordlist."""
91
+ candidates = [
92
+ Path(__file__).parent.parent.parent / "wordlists" / "ftp_creds.txt",
93
+ Path.cwd() / "wordlists" / "ftp_creds.txt",
94
+ ]
95
+ for p in candidates:
96
+ if p.exists():
97
+ return p
98
+ return None
99
+
100
+
101
+ # ── Core parser ───────────────────────────────────────────────────────────────
102
+
103
+ def _normalize_vendor_from_section(header: str) -> str:
104
+ """
105
+ Extract and normalise vendor key from a section header string.
106
+
107
+ Example: 'HP (Hewlett-Packard)' → 'hp'
108
+ """
109
+ # Strip parenthetical remarks
110
+ clean = re.sub(r'\(.*?\)', '', header).strip().lower()
111
+ # Remove trailing dashes/spaces
112
+ clean = clean.rstrip('-─ ')
113
+ return clean
114
+
115
+
116
+ def _parse_line(line: str) -> Optional[Tuple[str, Optional[str]]]:
117
+ """
118
+ Parse a single wordlist line into (username, password).
119
+
120
+ Returns None for blank/comment lines.
121
+ Password == None means blank/empty password.
122
+ """
123
+ stripped = line.strip()
124
+ if not stripped or stripped.startswith('#'):
125
+ return None
126
+
127
+ if ':' not in stripped:
128
+ # Bare word → treat as username with blank password
129
+ return (stripped, None)
130
+
131
+ # Split on FIRST colon only
132
+ idx = stripped.index(':')
133
+ username = stripped[:idx]
134
+ pwd_raw = stripped[idx + 1:]
135
+
136
+ password: Optional[str] = pwd_raw if pwd_raw else None
137
+ return (username, password)
138
+
139
+
140
+ def load_wordlist(
141
+ path: Optional[str | Path] = None,
142
+ protocol: str = 'any',
143
+ ) -> List[Cred]:
144
+ """
145
+ Load all credentials from a wordlist file.
146
+
147
+ Args:
148
+ path: Path to wordlist file. If None, auto-finds the default.
149
+ protocol: Protocol tag to assign to all loaded Cred entries.
150
+
151
+ Returns:
152
+ List of Cred objects (may be empty if file not found).
153
+ """
154
+ if path is None:
155
+ path = _find_default_wordlist()
156
+
157
+ if path is None:
158
+ logger.warning("[wordlist] Default wordlist not found; returning empty list")
159
+ return []
160
+
161
+ path = Path(path)
162
+ if not path.exists():
163
+ logger.warning("[wordlist] Wordlist not found: %s", path)
164
+ return []
165
+
166
+ creds: List[Cred] = []
167
+ seen: Set[Tuple[str, Optional[str]]] = set()
168
+ current_section = 'generic'
169
+ current_proto = protocol
170
+
171
+ try:
172
+ with open(path, encoding='utf-8', errors='ignore') as fh:
173
+ for lineno, raw_line in enumerate(fh, 1):
174
+ line = raw_line.rstrip('\n\r')
175
+
176
+ # Check for section header: # ── Vendor ────
177
+ m = _SECTION_RE.match(line)
178
+ if m:
179
+ current_section = _normalize_vendor_from_section(m.group(1))
180
+ # Infer protocol from section name
181
+ if 'snmp' in current_section:
182
+ current_proto = 'snmp'
183
+ elif 'ftp' in current_section:
184
+ current_proto = 'ftp'
185
+ elif 'telnet' in current_section:
186
+ current_proto = 'telnet'
187
+ else:
188
+ current_proto = protocol
189
+ continue
190
+
191
+ parsed = _parse_line(line)
192
+ if parsed is None:
193
+ continue
194
+
195
+ username, password = parsed
196
+ key = (username, password)
197
+ if key in seen:
198
+ continue
199
+ seen.add(key)
200
+
201
+ # Determine section vendor for the Cred notes field
202
+ creds.append(Cred(
203
+ username=username,
204
+ password=password,
205
+ protocol=current_proto,
206
+ notes=current_section,
207
+ ))
208
+
209
+ except OSError as exc:
210
+ logger.error("[wordlist] Failed to read %s: %s", path, exc)
211
+
212
+ logger.debug("[wordlist] Loaded %d entries from %s", len(creds), path)
213
+ return creds
214
+
215
+
216
+ def load_snmp_communities(path: Optional[str | Path] = None) -> List[str]:
217
+ """
218
+ Load SNMP community strings from snmp_communities.txt.
219
+
220
+ Returns list of community strings (one per non-comment line).
221
+ """
222
+ if path is None:
223
+ path = _find_snmp_wordlist()
224
+ if path is None:
225
+ return ["public", "private", "internal"]
226
+
227
+ communities: List[str] = []
228
+ seen: Set[str] = set()
229
+ try:
230
+ with open(path, encoding='utf-8', errors='ignore') as fh:
231
+ for line in fh:
232
+ s = line.strip()
233
+ if not s or s.startswith('#'):
234
+ continue
235
+ if s not in seen:
236
+ seen.add(s)
237
+ communities.append(s)
238
+ except OSError:
239
+ pass
240
+ return communities
241
+
242
+
243
+ def load_ftp_creds(path: Optional[str | Path] = None) -> List[Cred]:
244
+ """
245
+ Load FTP credentials from ftp_creds.txt.
246
+
247
+ Falls back to loading from the main wordlist FTP section if file not found.
248
+ """
249
+ if path is None:
250
+ path = _find_ftp_wordlist()
251
+ if path is None:
252
+ # Pull from main wordlist
253
+ return [c for c in load_wordlist() if c.protocol in ('ftp', 'any')]
254
+
255
+ return load_wordlist(path=path, protocol='ftp')
256
+
257
+
258
+ # ── Vendor-aware loader ───────────────────────────────────────────────────────
259
+
260
+ def load_for_vendor(
261
+ vendor: str,
262
+ wordlist_path: Optional[str | Path] = None,
263
+ include_generic: bool = True,
264
+ ) -> List[Cred]:
265
+ """
266
+ Load credentials relevant to a specific vendor from a wordlist.
267
+
268
+ The wordlist is parsed into sections; credentials from the matching
269
+ vendor section are returned first, followed by GENERIC/UNIVERSAL entries.
270
+
271
+ Args:
272
+ vendor: Vendor name (case-insensitive, e.g. 'epson', 'hp').
273
+ wordlist_path: Custom wordlist. If None, uses the default wordlist.
274
+ include_generic: Whether to append GENERIC/UNIVERSAL section entries.
275
+
276
+ Returns:
277
+ Ordered list of Cred objects, deduplicated.
278
+ """
279
+ if wordlist_path is None:
280
+ wl_path = _find_default_wordlist()
281
+ else:
282
+ wl_path = Path(wordlist_path)
283
+
284
+ if wl_path is None or not wl_path.exists():
285
+ logger.warning("[wordlist] Wordlist not found for vendor=%r; using empty list", vendor)
286
+ return []
287
+
288
+ # Normalize vendor key
289
+ key = vendor.lower().strip()
290
+ key = _ALIASES.get(key, key)
291
+
292
+ # Partial match support
293
+ all_keys = list(_ALIASES.values()) + list(_ALIASES.keys())
294
+ if key not in all_keys:
295
+ for alias_key in _ALIASES:
296
+ if alias_key in key or key in alias_key:
297
+ key = _ALIASES[alias_key]
298
+ break
299
+
300
+ # Parse the file into sections
301
+ vendor_creds: List[Cred] = []
302
+ generic_creds: List[Cred] = []
303
+ seen: Set[Tuple[str, Optional[str]]] = set()
304
+
305
+ current_section = 'generic'
306
+ current_proto = 'http'
307
+
308
+ try:
309
+ with open(wl_path, encoding='utf-8', errors='ignore') as fh:
310
+ for raw_line in fh:
311
+ line = raw_line.rstrip('\n\r')
312
+
313
+ m = _SECTION_RE.match(line)
314
+ if m:
315
+ current_section = _normalize_vendor_from_section(m.group(1))
316
+ if 'snmp' in current_section:
317
+ current_proto = 'snmp'
318
+ elif 'ftp' in current_section:
319
+ current_proto = 'ftp'
320
+ elif 'telnet' in current_section:
321
+ current_proto = 'telnet'
322
+ else:
323
+ current_proto = 'http'
324
+ continue
325
+
326
+ parsed = _parse_line(line)
327
+ if parsed is None:
328
+ continue
329
+
330
+ username, password = parsed
331
+ dedup_key = (username, password)
332
+
333
+ cred = Cred(username=username, password=password,
334
+ protocol=current_proto, notes=current_section)
335
+
336
+ # Determine if this section matches the requested vendor
337
+ section_key = _ALIASES.get(current_section, current_section)
338
+ is_vendor_match = (
339
+ section_key == key
340
+ or key in current_section
341
+ or current_section in key
342
+ or any(k in current_section for k in key.split())
343
+ )
344
+ is_generic = current_section in _GENERIC_SECTIONS
345
+
346
+ if is_vendor_match and dedup_key not in seen:
347
+ seen.add(dedup_key)
348
+ vendor_creds.append(cred)
349
+ elif is_generic and include_generic and dedup_key not in seen:
350
+ seen.add(dedup_key)
351
+ generic_creds.append(cred)
352
+
353
+ except OSError as exc:
354
+ logger.error("[wordlist] Failed to read %s: %s", wl_path, exc)
355
+
356
+ # Token entries: always add __SERIAL__, __MAC6__, __MAC12__ variants
357
+ # These are expanded at runtime in login_bruteforce.py
358
+ token_entries = [
359
+ Cred('admin', SERIAL_TOKEN, 'http', 'serial number as password'),
360
+ Cred('', SERIAL_TOKEN, 'http', 'blank user + serial'),
361
+ Cred('admin', MAC6_TOKEN, 'http', 'last 6 of MAC'),
362
+ Cred('admin', MAC12_TOKEN, 'http', 'full MAC'),
363
+ ]
364
+ for tc in token_entries:
365
+ k = (tc.username, tc.password)
366
+ if k not in seen:
367
+ seen.add(k)
368
+ vendor_creds.append(tc)
369
+
370
+ result = vendor_creds + generic_creds
371
+ logger.debug("[wordlist] vendor=%r → %d vendor + %d generic = %d total",
372
+ vendor, len(vendor_creds), len(generic_creds), len(result))
373
+ return result
374
+
375
+
376
+ # ── Convenience: default wordlist path ───────────────────────────────────────
377
+
378
+ def get_default_wordlist_path() -> Optional[str]:
379
+ """Return the path to the default wordlist as a string, or None."""
380
+ p = _find_default_wordlist()
381
+ return str(p) if p else None
382
+
383
+
384
+ def wordlist_stats(path: Optional[str | Path] = None) -> Dict[str, int]:
385
+ """
386
+ Return stats about a wordlist file: total entries and count per vendor section.
387
+
388
+ Args:
389
+ path: Wordlist path. If None, uses the default wordlist.
390
+
391
+ Returns:
392
+ Dict mapping section name → count of credential entries.
393
+ """
394
+ if path is None:
395
+ path = _find_default_wordlist()
396
+ if path is None:
397
+ return {}
398
+
399
+ stats: Dict[str, int] = {}
400
+ current_section = 'generic'
401
+ try:
402
+ with open(path, encoding='utf-8', errors='ignore') as fh:
403
+ for line in fh:
404
+ m = _SECTION_RE.match(line.rstrip())
405
+ if m:
406
+ current_section = _normalize_vendor_from_section(m.group(1))
407
+ continue
408
+ if _parse_line(line) is not None:
409
+ stats[current_section] = stats.get(current_section, 0) + 1
410
+ except OSError:
411
+ pass
412
+ return stats
src/version.py ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ PrinterXPL-Forge — version control module.
5
+
6
+ Versioning scheme: MAJOR.MINOR.PATCH (semver-inspired)
7
+ MAJOR — breaking changes or major new feature sets
8
+ MINOR — new features, backwards-compatible
9
+ PATCH — bug fixes, small improvements
10
+ """
11
+
12
+ # Author : Andre Henrique (@mrhenrike)
13
+ # GitHub : https://github.com/mrhenrike
14
+ # LinkedIn : https://linkedin.com/in/mrhenrike
15
+ # X/Twitter : https://x.com/mrhenrike
16
+
17
+ __version__ = "6.2.0"
18
+ __version_info__ = (6, 2, 0)
19
+ __release_date__ = "2026-05-12"
20
+ __author__ = "Andre Henrique"
21
+ __license__ = "MIT"
22
+
23
+
24
+ def get_version() -> str:
25
+ """Return the bare version string, e.g. '3.0.0'."""
26
+ return __version__
27
+
28
+
29
+ def get_version_info() -> tuple:
30
+ """Return the version as a (major, minor, patch) tuple."""
31
+ return __version_info__
32
+
33
+
34
+ def get_version_string() -> str:
35
+ """Return formatted version string used in the banner."""
36
+ return f"Version {__version__} ({__release_date__})"