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
src/core/capabilities.py
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Author : Andre Henrique (@mrhenrike)
|
|
6
|
+
# GitHub : https://github.com/mrhenrike
|
|
7
|
+
# LinkedIn : https://linkedin.com/in/mrhenrike
|
|
8
|
+
# X/Twitter : https://x.com/mrhenrike
|
|
9
|
+
|
|
10
|
+
# python standard library
|
|
11
|
+
import re
|
|
12
|
+
import os
|
|
13
|
+
import sys # , urllib.error, urllib.parse
|
|
14
|
+
|
|
15
|
+
import requests
|
|
16
|
+
import urllib3
|
|
17
|
+
|
|
18
|
+
# local pret classes
|
|
19
|
+
from utils.helper import output, item
|
|
20
|
+
|
|
21
|
+
# ── SNMP backend selection ───────────────────────────────────────────────────
|
|
22
|
+
# Priority: pysnmp-lextudio v5+ hlapi (sync) → pysnmp v4 oneliner (legacy)
|
|
23
|
+
# pysnmp v7 (community fork) ships an asyncio-only API; we detect and use
|
|
24
|
+
# the synchronous shim when available.
|
|
25
|
+
_SNMP_BACKEND = None
|
|
26
|
+
|
|
27
|
+
import warnings as _pysnmp_warn
|
|
28
|
+
_pysnmp_warn.filterwarnings("ignore", category=RuntimeWarning)
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
# pysnmp-lextudio ≥5 (hlapi synchronous, Python 3.8+) — primary backend
|
|
32
|
+
from pysnmp.hlapi import ( # type: ignore
|
|
33
|
+
getCmd, nextCmd, CommunityData, UdpTransportTarget,
|
|
34
|
+
ContextData, ObjectType, ObjectIdentity, SnmpEngine,
|
|
35
|
+
)
|
|
36
|
+
_SNMP_BACKEND = 'hlapi-v5'
|
|
37
|
+
except ImportError:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
if _SNMP_BACKEND is None:
|
|
41
|
+
try:
|
|
42
|
+
# pysnmp ≥7 community version exposes hlapi.asyncio; provide a sync shim
|
|
43
|
+
from pysnmp.hlapi.asyncio import ( # type: ignore
|
|
44
|
+
getCmd as _asyncGetCmd, CommunityData, UdpTransportTarget,
|
|
45
|
+
ContextData, ObjectType, ObjectIdentity, SnmpEngine,
|
|
46
|
+
)
|
|
47
|
+
import asyncio as _asyncio
|
|
48
|
+
|
|
49
|
+
def getCmd(*args, **kwargs): # type: ignore[override]
|
|
50
|
+
"""Synchronous shim wrapping the asyncio getCmd."""
|
|
51
|
+
return _asyncio.get_event_loop().run_until_complete(
|
|
52
|
+
_asyncGetCmd(*args, **kwargs)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_SNMP_BACKEND = 'hlapi-v7'
|
|
56
|
+
except ImportError:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
if _SNMP_BACKEND is None:
|
|
60
|
+
try:
|
|
61
|
+
# pysnmp ≤4.x oneliner (legacy, deprecated in 3.12+)
|
|
62
|
+
from pysnmp.entity.rfc3413.oneliner import cmdgen # type: ignore
|
|
63
|
+
_SNMP_BACKEND = 'oneliner'
|
|
64
|
+
except ImportError:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class capabilities():
|
|
69
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
70
|
+
# set defaults
|
|
71
|
+
support = False
|
|
72
|
+
# default timeout - can be overridden in __init__
|
|
73
|
+
timeout = 1.5
|
|
74
|
+
# set pret.py directory
|
|
75
|
+
rundir = os.path.dirname(os.path.realpath(__file__)) + os.path.sep
|
|
76
|
+
'''
|
|
77
|
+
┌──────────────────────────────────────────────────────────┐
|
|
78
|
+
│ how to get printer's capabilities? │
|
|
79
|
+
├──────┬───────────────────────┬───────────────────────────┤
|
|
80
|
+
│ │ model (for db lookup) │ lang (ps/pjl/pcl support) │
|
|
81
|
+
├──────┼───────────────────────┼───────────────────────────┤
|
|
82
|
+
│ IPP │ printer-description │ printer-description │
|
|
83
|
+
│ SNMP │ hrDeviceDescr │ prtInterpreterDescription │
|
|
84
|
+
│ HTTP │ html-title │ - │
|
|
85
|
+
| HTTPS| html-title | - |
|
|
86
|
+
└──────┴───────────────────────┴───────────────────────────┘
|
|
87
|
+
'''
|
|
88
|
+
|
|
89
|
+
def __init__(self, args, timeout=None):
|
|
90
|
+
# allow custom timeout to be passed
|
|
91
|
+
if timeout is not None:
|
|
92
|
+
self.timeout = timeout
|
|
93
|
+
# skip this in unsafe mode
|
|
94
|
+
if not args.safe:
|
|
95
|
+
return
|
|
96
|
+
# set printer language
|
|
97
|
+
if args.mode == 'ps':
|
|
98
|
+
lang = ["PS", "PostScript", "BR-Script", "KPDL"]
|
|
99
|
+
if args.mode == 'pjl':
|
|
100
|
+
lang = ["PJL"]
|
|
101
|
+
if args.mode == 'pcl':
|
|
102
|
+
lang = ["PCL"]
|
|
103
|
+
# get list of PostScript/PJL/PCL capable printers
|
|
104
|
+
self.models = self.get_models(args.mode + ".dat")
|
|
105
|
+
# try to get printer capabilities via IPP/SNMP/HTTP
|
|
106
|
+
if not self.support:
|
|
107
|
+
self.ipp(args.target, lang)
|
|
108
|
+
if not self.support:
|
|
109
|
+
self.http(args.target)
|
|
110
|
+
if not self.support:
|
|
111
|
+
self.https(args.target)
|
|
112
|
+
if not self.support:
|
|
113
|
+
self.snmp(args.target, lang)
|
|
114
|
+
# feedback on PostScript/PJL/PCL support
|
|
115
|
+
self.feedback(self.support, lang[0])
|
|
116
|
+
# in safe mode, exit if unsupported
|
|
117
|
+
if args.safe and not self.support:
|
|
118
|
+
print((os.linesep + "Quitting as we are in safe mode."))
|
|
119
|
+
sys.exit()
|
|
120
|
+
print("")
|
|
121
|
+
|
|
122
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
123
|
+
# get capabilities via IPP (HTTP or HTTPS fallback)
|
|
124
|
+
def ipp(self, host, lang):
|
|
125
|
+
try:
|
|
126
|
+
sys.stdout.write("Checking for IPP support: ")
|
|
127
|
+
# IPP 1.1 GET-PRINTER-ATTRIBUTES request (binary)
|
|
128
|
+
body = (
|
|
129
|
+
b'\x01\x01\x00\x0b\x00\x01\xab\x10'
|
|
130
|
+
b'\x01G\x00\x12attributes-charset\x00\x05utf-8'
|
|
131
|
+
b'H\x00\x1battributes-natural-language\x00\x02en'
|
|
132
|
+
b'E\x00\x0bprinter-uri\x00\x14ipp://localhost/ipp/'
|
|
133
|
+
b'D\x00\x14requested-attributes\x00\x13printer-description\x03'
|
|
134
|
+
)
|
|
135
|
+
headers = {'Content-type': 'application/ipp'}
|
|
136
|
+
response_bytes = None
|
|
137
|
+
|
|
138
|
+
# IPP endpoints to probe (in priority order)
|
|
139
|
+
ipp_candidates = [
|
|
140
|
+
('http', host, 631, '/'),
|
|
141
|
+
('http', host, 631, '/ipp/'),
|
|
142
|
+
('http', host, 631, '/ipp/print'),
|
|
143
|
+
('https', host, 631, '/ipp/print'),
|
|
144
|
+
('https', host, 631, '/ipp/'),
|
|
145
|
+
('https', host, 631, '/'),
|
|
146
|
+
]
|
|
147
|
+
for scheme, h, port, path in ipp_candidates:
|
|
148
|
+
try:
|
|
149
|
+
url = f"{scheme}://{h}:{port}{path}"
|
|
150
|
+
r = requests.post(url, data=body, headers=headers,
|
|
151
|
+
timeout=self.timeout * 2,
|
|
152
|
+
verify=False)
|
|
153
|
+
# 200 = success; 426 means TLS required → continue loop
|
|
154
|
+
if r.status_code == 200 and len(r.content) > 8:
|
|
155
|
+
response_bytes = r.content
|
|
156
|
+
break
|
|
157
|
+
except Exception:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
if response_bytes is None:
|
|
161
|
+
raise Exception("no IPP endpoint responded")
|
|
162
|
+
|
|
163
|
+
# Decode as latin-1 so binary bytes are preserved 1:1
|
|
164
|
+
response = response_bytes.decode('latin-1')
|
|
165
|
+
|
|
166
|
+
# Extract printer-device-id field (contains MDL: and CMD:)
|
|
167
|
+
# It appears as a length-prefixed string in IPP binary
|
|
168
|
+
model = item(re.findall(r"MDL:(.+?);", response))
|
|
169
|
+
langs = item(re.findall(r"CMD:(.+?);", response))
|
|
170
|
+
|
|
171
|
+
# Also scan document-format-supported for ESC/P, PWGRaster etc.
|
|
172
|
+
doc_fmts = re.findall(r"(application/vnd\.epson\.[^;\x00]+|"
|
|
173
|
+
r"application/postscript|"
|
|
174
|
+
r"application/pcl|"
|
|
175
|
+
r"image/pwg-raster)", response, re.I)
|
|
176
|
+
if doc_fmts:
|
|
177
|
+
self.doc_formats = doc_fmts
|
|
178
|
+
output().chitchat(f" IPP document formats: {', '.join(doc_fmts)}")
|
|
179
|
+
|
|
180
|
+
# Try to set language support from CMD: field
|
|
181
|
+
self.support = [x for x in [re.findall(
|
|
182
|
+
re.escape(pdl), langs, re.I) for pdl in lang] if x]
|
|
183
|
+
self.set_support(model)
|
|
184
|
+
output().green("found")
|
|
185
|
+
except Exception as e:
|
|
186
|
+
output().errmsg("not found", e)
|
|
187
|
+
|
|
188
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
189
|
+
# get capabilities via HTTP
|
|
190
|
+
def http(self, host):
|
|
191
|
+
try:
|
|
192
|
+
sys.stdout.write("Checking for HTTP support: ")
|
|
193
|
+
# allow_redirects=False so an HTTP→HTTPS redirect doesn't cause SSL errors
|
|
194
|
+
html = requests.get(
|
|
195
|
+
"http://" + host, verify=False,
|
|
196
|
+
allow_redirects=False, timeout=self.timeout,
|
|
197
|
+
).text
|
|
198
|
+
title = re.findall("<title.*?>\n?(.+?)\n?</title>",
|
|
199
|
+
html, re.I | re.M | re.S)
|
|
200
|
+
model = item(title)
|
|
201
|
+
self.set_support(model)
|
|
202
|
+
output().green("found")
|
|
203
|
+
except Exception as e:
|
|
204
|
+
output().errmsg("not found", e)
|
|
205
|
+
|
|
206
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
207
|
+
# get capabilities via HTTPS
|
|
208
|
+
def https(self, host):
|
|
209
|
+
try:
|
|
210
|
+
# poor man's way get https title
|
|
211
|
+
sys.stdout.write("Checking for HTTPS support: ")
|
|
212
|
+
html = requests.get("https://" + host, verify=False).text
|
|
213
|
+
# cause we are to parsimonious to import BeautifulSoup ;)
|
|
214
|
+
title = re.findall("<title.*?>\n?(.+?)\n?</title>",
|
|
215
|
+
html, re.I | re.M | re.S)
|
|
216
|
+
# get name of device
|
|
217
|
+
model = item(title)
|
|
218
|
+
# get language support
|
|
219
|
+
self.set_support(model)
|
|
220
|
+
output().green("found")
|
|
221
|
+
except Exception as e:
|
|
222
|
+
output().errmsg("not found", e)
|
|
223
|
+
|
|
224
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
225
|
+
# get capabilities via SNMP
|
|
226
|
+
def snmp(self, host, lang):
|
|
227
|
+
try:
|
|
228
|
+
sys.stdout.write("Checking for SNMP support: ")
|
|
229
|
+
if _SNMP_BACKEND is None:
|
|
230
|
+
raise NameError("pysnmp not installed")
|
|
231
|
+
|
|
232
|
+
# HOST-RESOURCES-MIB → hrDeviceDescr
|
|
233
|
+
desc_oid = '1.3.6.1.2.1.25.3.2.1.3'
|
|
234
|
+
# Printer-MIB → prtInterpreterDescription
|
|
235
|
+
pdls_oid = '1.3.6.1.2.1.43.15.1.1.5.1'
|
|
236
|
+
desc, pdls = [], []
|
|
237
|
+
|
|
238
|
+
if _SNMP_BACKEND in ('hlapi-v5', 'hlapi-v7'):
|
|
239
|
+
# pysnmp-lextudio v5 synchronous hlapi (also v7 via shim)
|
|
240
|
+
engine = SnmpEngine()
|
|
241
|
+
community = CommunityData('public', mpModel=0)
|
|
242
|
+
transport = UdpTransportTarget(
|
|
243
|
+
(host, 161), timeout=self.timeout, retries=0)
|
|
244
|
+
context = ContextData()
|
|
245
|
+
|
|
246
|
+
for oid_str, bucket in [(desc_oid, desc), (pdls_oid, pdls)]:
|
|
247
|
+
for err_ind, err_stat, _, var_binds in nextCmd(
|
|
248
|
+
engine, community, transport, context,
|
|
249
|
+
ObjectType(ObjectIdentity(oid_str)),
|
|
250
|
+
lexicographicMode=False,
|
|
251
|
+
):
|
|
252
|
+
if err_ind:
|
|
253
|
+
break
|
|
254
|
+
if err_stat:
|
|
255
|
+
break
|
|
256
|
+
for var_bind in var_binds:
|
|
257
|
+
bucket.append(str(var_bind[1]))
|
|
258
|
+
|
|
259
|
+
elif _SNMP_BACKEND == 'oneliner':
|
|
260
|
+
# Legacy oneliner API (pysnmp ≤ 4.x, Python 3.8–3.11)
|
|
261
|
+
error, error_status, _idx, binds = cmdgen.CommandGenerator().nextCmd( # type: ignore
|
|
262
|
+
cmdgen.CommunityData('public', mpModel=0),
|
|
263
|
+
cmdgen.UdpTransportTarget((host, 161), timeout=self.timeout, retries=0),
|
|
264
|
+
desc_oid, pdls_oid,
|
|
265
|
+
)
|
|
266
|
+
if error:
|
|
267
|
+
raise Exception(error)
|
|
268
|
+
if error_status:
|
|
269
|
+
raise Exception(error_status.prettyPrint())
|
|
270
|
+
for row in binds:
|
|
271
|
+
for key, val in row:
|
|
272
|
+
if desc_oid in str(key):
|
|
273
|
+
desc.append(str(val))
|
|
274
|
+
if pdls_oid in str(key):
|
|
275
|
+
pdls.append(str(val))
|
|
276
|
+
|
|
277
|
+
# get name of device
|
|
278
|
+
model = item(desc)
|
|
279
|
+
# get language support
|
|
280
|
+
langs = ','.join(pdls)
|
|
281
|
+
self.support = [x for x in [re.findall(
|
|
282
|
+
re.escape(pdl), langs, re.I) for pdl in lang] if x]
|
|
283
|
+
output().green("found")
|
|
284
|
+
except NameError:
|
|
285
|
+
output().errmsg("not found", "pysnmp module not installed")
|
|
286
|
+
except Exception as e:
|
|
287
|
+
output().errmsg("not found", e)
|
|
288
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
289
|
+
# feedback on language support
|
|
290
|
+
|
|
291
|
+
def feedback(self, support, lang):
|
|
292
|
+
sys.stdout.write("Checking for %-21s" % (lang + " support: "))
|
|
293
|
+
if support:
|
|
294
|
+
output().green("found")
|
|
295
|
+
else:
|
|
296
|
+
output().warning("not found")
|
|
297
|
+
|
|
298
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
299
|
+
# set language support
|
|
300
|
+
def set_support(self, model):
|
|
301
|
+
# model_stripped = re.sub(r'(\d|\s|-)[a-zA-Z]+$', '', model)
|
|
302
|
+
'''
|
|
303
|
+
┌───────────────────────────────────────────────────────┐
|
|
304
|
+
│ Experimental -- This might introduce false positives! │
|
|
305
|
+
├───────────────────────────────────────────────────────┤
|
|
306
|
+
│ The stripped down version of the model string removes │
|
|
307
|
+
│ endings like '-series', ' printer' (maybe localized), │
|
|
308
|
+
│ 'd' (duplex), 't' (tray), 'c' (color), 'n' (network). │
|
|
309
|
+
└───────────────────────────────────────────────────────┘
|
|
310
|
+
'''
|
|
311
|
+
self.support = [x for x in [re.findall(
|
|
312
|
+
re.escape(m), model, re.I) for m in self.models] if x]
|
|
313
|
+
|
|
314
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
315
|
+
# open database of supported devices
|
|
316
|
+
def get_models(self, file):
|
|
317
|
+
try:
|
|
318
|
+
with open(self.rundir + "db" + os.path.sep + file, 'r') as f:
|
|
319
|
+
models = [line.strip() for line in f if line.strip()]
|
|
320
|
+
return models
|
|
321
|
+
except IOError as e:
|
|
322
|
+
output().errmsg("Cannot open file", e)
|
|
323
|
+
return []
|