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,307 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Local Installed Printer Discovery
|
|
5
|
+
==================================
|
|
6
|
+
Enumerates printers installed on the local host across all platforms.
|
|
7
|
+
|
|
8
|
+
Supports:
|
|
9
|
+
- Windows : Get-Printer + Get-PrinterPort (PowerShell)
|
|
10
|
+
- Linux : CUPS via lpstat or /etc/cups/printers.conf
|
|
11
|
+
- macOS : CUPS via lpstat
|
|
12
|
+
- Android : CUPS via lpstat (if installed)
|
|
13
|
+
- BSD : CUPS via lpstat
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# Author : Andre Henrique (@mrhenrike)
|
|
17
|
+
# GitHub : https://github.com/mrhenrike
|
|
18
|
+
# LinkedIn : https://linkedin.com/in/mrhenrike
|
|
19
|
+
# X/Twitter : https://x.com/mrhenrike
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
import shutil
|
|
25
|
+
import subprocess
|
|
26
|
+
import socket
|
|
27
|
+
from typing import Dict, List, Optional
|
|
28
|
+
|
|
29
|
+
from core.osdetect import get_os
|
|
30
|
+
from utils.helper import output
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ── dataclass-like dict keys ─────────────────────────────────────────────────
|
|
34
|
+
# Each printer entry dict has these keys (all strings):
|
|
35
|
+
# name, driver, port, ip, protocol, status, shared, source
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def discover_local_installed() -> List[Dict[str, str]]:
|
|
39
|
+
"""
|
|
40
|
+
Enumerate all printers installed on this host.
|
|
41
|
+
|
|
42
|
+
Returns a list of dicts with printer info.
|
|
43
|
+
Each dict has: name, driver, port, ip, protocol, status, shared, source.
|
|
44
|
+
"""
|
|
45
|
+
os_type = get_os()
|
|
46
|
+
|
|
47
|
+
if os_type in ('windows', 'wsl'):
|
|
48
|
+
return _windows_printers()
|
|
49
|
+
elif os_type in ('linux', 'darwin', 'bsd', 'android', 'wsl'):
|
|
50
|
+
return _cups_printers()
|
|
51
|
+
else:
|
|
52
|
+
output().warning("Local printer discovery not supported on this OS.")
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ── Windows ──────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
def _windows_printers() -> List[Dict[str, str]]:
|
|
59
|
+
"""Use PowerShell Get-Printer to list installed printers, then resolve IPs."""
|
|
60
|
+
pwsh = shutil.which('powershell.exe') or shutil.which('pwsh.exe')
|
|
61
|
+
if not pwsh:
|
|
62
|
+
output().warning("PowerShell not found; cannot enumerate local printers.")
|
|
63
|
+
return []
|
|
64
|
+
|
|
65
|
+
script = (
|
|
66
|
+
"Get-Printer | Select-Object Name,DriverName,PortName,PrinterStatus,Shared"
|
|
67
|
+
" | ConvertTo-Json -Compress"
|
|
68
|
+
)
|
|
69
|
+
try:
|
|
70
|
+
raw = subprocess.check_output(
|
|
71
|
+
[pwsh, '-NoProfile', '-Command', script],
|
|
72
|
+
text=True, timeout=20, stderr=subprocess.DEVNULL,
|
|
73
|
+
)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
output().warning(f"Get-Printer failed: {e}")
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
import json
|
|
79
|
+
try:
|
|
80
|
+
data = json.loads(raw) if raw.strip() else []
|
|
81
|
+
except json.JSONDecodeError:
|
|
82
|
+
output().warning("Could not parse Get-Printer JSON output.")
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
if isinstance(data, dict):
|
|
86
|
+
data = [data]
|
|
87
|
+
|
|
88
|
+
results = []
|
|
89
|
+
for p in data:
|
|
90
|
+
name = str(p.get('Name', ''))
|
|
91
|
+
driver = str(p.get('DriverName', ''))
|
|
92
|
+
port = str(p.get('PortName', ''))
|
|
93
|
+
status = _win_status(str(p.get('PrinterStatus', '0')))
|
|
94
|
+
shared = 'yes' if p.get('Shared') else 'no'
|
|
95
|
+
|
|
96
|
+
ip, proto = _resolve_win_printer(port, name)
|
|
97
|
+
|
|
98
|
+
results.append({
|
|
99
|
+
'name': name,
|
|
100
|
+
'driver': driver,
|
|
101
|
+
'port': port,
|
|
102
|
+
'ip': ip,
|
|
103
|
+
'protocol': proto,
|
|
104
|
+
'status': status,
|
|
105
|
+
'shared': shared,
|
|
106
|
+
'source': 'windows-local',
|
|
107
|
+
})
|
|
108
|
+
return results
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _resolve_win_printer(port_name: str, printer_name: str) -> tuple:
|
|
112
|
+
"""
|
|
113
|
+
Resolve IP and protocol from a Windows printer port name and printer name.
|
|
114
|
+
|
|
115
|
+
Returns (ip_str, protocol_str).
|
|
116
|
+
"""
|
|
117
|
+
# Pattern: IP_x.x.x.x or IP_x.x.x.x_N (standard TCP/IP port monitor)
|
|
118
|
+
m = re.match(r'IP_(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', port_name)
|
|
119
|
+
if m:
|
|
120
|
+
return m.group(1), 'TCP/9100'
|
|
121
|
+
|
|
122
|
+
# Pattern: direct IP in port name (e.g. '192.168.1.5:9100')
|
|
123
|
+
m = re.match(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::(\d+))?', port_name)
|
|
124
|
+
if m:
|
|
125
|
+
return m.group(1), f"TCP/{m.group(2) or '9100'}"
|
|
126
|
+
|
|
127
|
+
# Pattern: Epson port like EP3F9F9C:L3250 SERIES
|
|
128
|
+
m = re.match(r'EP([0-9A-Fa-f]{4,8})', port_name, re.I)
|
|
129
|
+
if m:
|
|
130
|
+
hostname = 'EPSON' + m.group(1).upper()
|
|
131
|
+
ip = _try_resolve(hostname) or ''
|
|
132
|
+
return ip, 'WSD/IPP'
|
|
133
|
+
|
|
134
|
+
# WSD port: try to resolve from the printer name
|
|
135
|
+
# Windows WSD printer names often start with the device hostname
|
|
136
|
+
# e.g. "EPSON3F9F9C (L3250 Series)", "PROMETHEUSPRINTER (HP DeskJet 2700 series)"
|
|
137
|
+
if port_name.startswith('WSD-') or port_name.startswith('wsd-'):
|
|
138
|
+
hostname = printer_name.split(' ')[0].strip()
|
|
139
|
+
ip = _try_resolve(hostname) or ''
|
|
140
|
+
proto = 'WSD/IPP'
|
|
141
|
+
# Fallback: try extracting hostname from parentheses if first word fails
|
|
142
|
+
if not ip:
|
|
143
|
+
m2 = re.search(r'\(([^)]+)\)', printer_name)
|
|
144
|
+
if m2:
|
|
145
|
+
ip = _try_resolve(m2.group(1).strip()) or ''
|
|
146
|
+
return ip, proto
|
|
147
|
+
|
|
148
|
+
# Virtual/local ports
|
|
149
|
+
virtual = ('nul:', 'PORTPROMPT:', 'SHRFAX:', 'USB001', 'FILE:',
|
|
150
|
+
'LPT', 'COM', 'XPS')
|
|
151
|
+
if any(port_name.startswith(v) for v in virtual):
|
|
152
|
+
return '', 'Virtual'
|
|
153
|
+
|
|
154
|
+
return '', 'WSD'
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _win_status(code: str) -> str:
|
|
158
|
+
"""Map Windows PrinterStatus integer to human-readable string."""
|
|
159
|
+
try:
|
|
160
|
+
n = int(code)
|
|
161
|
+
except ValueError:
|
|
162
|
+
return code
|
|
163
|
+
mapping = {
|
|
164
|
+
0: 'Ready',
|
|
165
|
+
1: 'Paused',
|
|
166
|
+
2: 'Error',
|
|
167
|
+
3: 'Pending Deletion',
|
|
168
|
+
4: 'Paper Jam',
|
|
169
|
+
5: 'Paper Out',
|
|
170
|
+
6: 'Manual Feed',
|
|
171
|
+
7: 'Paper Problem',
|
|
172
|
+
8: 'Offline',
|
|
173
|
+
16: 'Printing',
|
|
174
|
+
32: 'Output Bin Full',
|
|
175
|
+
64: 'Paper jam (alt)',
|
|
176
|
+
128: 'No Toner/Ink',
|
|
177
|
+
132: 'Offline (no toner)',
|
|
178
|
+
}
|
|
179
|
+
return mapping.get(n, f'Status:{n}')
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _try_resolve(hostname: str) -> Optional[str]:
|
|
183
|
+
"""Try to resolve a hostname to an IPv4 address."""
|
|
184
|
+
try:
|
|
185
|
+
return socket.gethostbyname(hostname)
|
|
186
|
+
except socket.gaierror:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ── CUPS (Linux / macOS / BSD / Android) ─────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
def _cups_printers() -> List[Dict[str, str]]:
|
|
193
|
+
"""Use lpstat -v to enumerate CUPS-managed printers."""
|
|
194
|
+
lpstat = shutil.which('lpstat')
|
|
195
|
+
if not lpstat:
|
|
196
|
+
output().warning("lpstat not found. Install CUPS client tools.")
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
results = []
|
|
200
|
+
try:
|
|
201
|
+
# lpstat -v: lists printer names and device URIs
|
|
202
|
+
raw_v = subprocess.check_output(
|
|
203
|
+
[lpstat, '-v'], text=True, timeout=10, stderr=subprocess.DEVNULL,
|
|
204
|
+
)
|
|
205
|
+
# lpstat -l -p: detailed printer info (status etc)
|
|
206
|
+
try:
|
|
207
|
+
raw_l = subprocess.check_output(
|
|
208
|
+
[lpstat, '-l', '-p'], text=True, timeout=10,
|
|
209
|
+
stderr=subprocess.DEVNULL,
|
|
210
|
+
)
|
|
211
|
+
except Exception:
|
|
212
|
+
raw_l = ''
|
|
213
|
+
|
|
214
|
+
# Parse "device for <name>: <uri>"
|
|
215
|
+
for line in raw_v.splitlines():
|
|
216
|
+
m = re.match(r'device for (.+?):\s+(.+)', line)
|
|
217
|
+
if not m:
|
|
218
|
+
continue
|
|
219
|
+
name, uri = m.group(1).strip(), m.group(2).strip()
|
|
220
|
+
ip, protocol = _parse_cups_uri(uri)
|
|
221
|
+
status = _cups_status(name, raw_l)
|
|
222
|
+
results.append({
|
|
223
|
+
'name': name,
|
|
224
|
+
'driver': '',
|
|
225
|
+
'port': uri,
|
|
226
|
+
'ip': ip,
|
|
227
|
+
'protocol': protocol,
|
|
228
|
+
'status': status,
|
|
229
|
+
'shared': '',
|
|
230
|
+
'source': 'cups-local',
|
|
231
|
+
})
|
|
232
|
+
except Exception as e:
|
|
233
|
+
output().warning(f"lpstat failed: {e}")
|
|
234
|
+
|
|
235
|
+
return results
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _parse_cups_uri(uri: str):
|
|
239
|
+
"""Parse a CUPS device URI and return (ip, protocol)."""
|
|
240
|
+
# socket://192.168.1.100:9100
|
|
241
|
+
m = re.match(r'socket://([^:/]+)(?::(\d+))?', uri)
|
|
242
|
+
if m:
|
|
243
|
+
ip = _try_resolve(m.group(1)) or m.group(1)
|
|
244
|
+
port = m.group(2) or '9100'
|
|
245
|
+
return ip, f'RAW/{port}'
|
|
246
|
+
|
|
247
|
+
# ipp://host:port/path or ipps://...
|
|
248
|
+
m = re.match(r'ipps?://([^:/]+)(?::(\d+))?(/.*)?', uri)
|
|
249
|
+
if m:
|
|
250
|
+
ip = _try_resolve(m.group(1)) or m.group(1)
|
|
251
|
+
port = m.group(2) or '631'
|
|
252
|
+
scheme = 'IPP' if uri.startswith('ipp://') else 'IPP/TLS'
|
|
253
|
+
return ip, f'{scheme}/{port}'
|
|
254
|
+
|
|
255
|
+
# lpd://host/queue
|
|
256
|
+
m = re.match(r'lpd://([^/]+)', uri)
|
|
257
|
+
if m:
|
|
258
|
+
ip = _try_resolve(m.group(1)) or m.group(1)
|
|
259
|
+
return ip, 'LPD/515'
|
|
260
|
+
|
|
261
|
+
# usb://Make/Model
|
|
262
|
+
if uri.startswith('usb://'):
|
|
263
|
+
return '', 'USB'
|
|
264
|
+
|
|
265
|
+
# dnssd:// or mdns://
|
|
266
|
+
if 'dnssd' in uri or 'mdns' in uri:
|
|
267
|
+
m = re.search(r'/([^/]+)/queue', uri)
|
|
268
|
+
hostname = m.group(1) if m else ''
|
|
269
|
+
ip = _try_resolve(hostname) or ''
|
|
270
|
+
return ip, 'Bonjour/IPP'
|
|
271
|
+
|
|
272
|
+
return '', uri.split('://')[0].upper()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _cups_status(name: str, lpstat_output: str) -> str:
|
|
276
|
+
"""Extract printer status from lpstat -l -p output."""
|
|
277
|
+
# Look for "printer <name> ..." section
|
|
278
|
+
m = re.search(
|
|
279
|
+
rf'printer {re.escape(name)}\s+(?:is\s+)?(\S+)',
|
|
280
|
+
lpstat_output, re.I,
|
|
281
|
+
)
|
|
282
|
+
if m:
|
|
283
|
+
return m.group(1).replace('-', ' ').title()
|
|
284
|
+
return '?'
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# ── Display ──────────────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
def print_local_printers(printers: List[Dict[str, str]]) -> None:
|
|
290
|
+
"""Pretty-print the list of local installed printers."""
|
|
291
|
+
if not printers:
|
|
292
|
+
print(" [!] No local printers found.")
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
header = f" {'HOST':<18} {'NAME':<35} {'PROTO':<12} {'STATUS':<14} {'DRIVER'}"
|
|
296
|
+
print(f"\033[1;32m{header}\033[0m")
|
|
297
|
+
print(" " + "─" * 98)
|
|
298
|
+
|
|
299
|
+
for p in printers:
|
|
300
|
+
host = (p['ip'] or '-').ljust(18)[:18]
|
|
301
|
+
name = (p['name'] or '-').ljust(35)[:35]
|
|
302
|
+
proto = (p['protocol'] or '-').ljust(12)[:12]
|
|
303
|
+
status = (p['status'] or '-').ljust(14)[:14]
|
|
304
|
+
driver = (p['driver'] or '-')[:30]
|
|
305
|
+
print(f" {host} {name} {proto} {status} {driver}")
|
|
306
|
+
|
|
307
|
+
print()
|