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,211 @@
|
|
|
1
|
+
local description = [[
|
|
2
|
+
printer-printnightmare — Windows Print Spooler vulnerability detection.
|
|
3
|
+
|
|
4
|
+
Detects exposure of the Windows Print Spooler service (spoolsv.exe) and assesses
|
|
5
|
+
vulnerability to the PrintNightmare family of exploits:
|
|
6
|
+
|
|
7
|
+
- CVE-2021-1675: PrintNightmare RCE via RpcAddPrinterDriverEx (unauthenticated + network)
|
|
8
|
+
- CVE-2021-34527: PrintNightmare (official patch bypass) — still exploitable in some configs
|
|
9
|
+
- CVE-2020-1048: PrintDemon — persistent SYSTEM backdoor via printer port race condition
|
|
10
|
+
- CVE-2020-1337: PrintDemon hardlink bypass — arbitrary privileged file write
|
|
11
|
+
- CVE-2021-36958: Windows Print Spooler remote code execution
|
|
12
|
+
- CVE-2022-38028: GooseEgg — APT28 Print Spooler LPE (CISA KEV 2024, actively exploited)
|
|
13
|
+
- CVE-2022-21999: SpoolFool — Windows Print Spooler LPE
|
|
14
|
+
|
|
15
|
+
Probes: SMB (445), RPC (135), and WSD (3702) to confirm spooler exposure.
|
|
16
|
+
|
|
17
|
+
Author: André Henrique (@mrhenrike) | União Geek
|
|
18
|
+
]]
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
-- @usage
|
|
22
|
+
-- nmap -p 445,135,3702 --script printer-printnightmare <target>
|
|
23
|
+
-- @output
|
|
24
|
+
-- PORT STATE SERVICE
|
|
25
|
+
-- 445/tcp open microsoft-ds
|
|
26
|
+
-- | printer-printnightmare:
|
|
27
|
+
-- | SMB : Open (Windows detected)
|
|
28
|
+
-- | OS : Windows 10 / Server 2019
|
|
29
|
+
-- | Spooler : POSSIBLY ACTIVE (SMB open, historical exposure)
|
|
30
|
+
-- | [CRITICAL] CVE-2021-1675 (CVSS 9.8) — PrintNightmare unauthenticated network RCE
|
|
31
|
+
-- | [HIGH] CVE-2022-38028 (CVSS 7.8) — GooseEgg LPE (APT28, CISA KEV)
|
|
32
|
+
-- | [HIGH] CVE-2020-1048 (CVSS 7.8) — PrintDemon persistent SYSTEM backdoor
|
|
33
|
+
-- | Verdict : POSSIBLY VULNERABLE
|
|
34
|
+
-- |_ Suggest : printerxpl-forge run --module xpl/edb-50498 --target <IP>
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
categories = { "vuln", "safe" }
|
|
38
|
+
author = "André Henrique (@mrhenrike) | União Geek"
|
|
39
|
+
license = "Same as Nmap -- See https://nmap.org/book/man-legal.html"
|
|
40
|
+
|
|
41
|
+
local stdnse = require "stdnse"
|
|
42
|
+
local shortport = require "shortport"
|
|
43
|
+
local smb = require "smb"
|
|
44
|
+
local smb2 = require "smb2"
|
|
45
|
+
local comm = require "comm"
|
|
46
|
+
local string = require "string"
|
|
47
|
+
local table = require "table"
|
|
48
|
+
|
|
49
|
+
portrule = shortport.port_or_service(
|
|
50
|
+
{ 445, 135, 3702, 139 },
|
|
51
|
+
{ "microsoft-ds", "msrpc", "wsd", "netbios-ssn" },
|
|
52
|
+
"tcp"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
local SPOOLER_CVES = {
|
|
56
|
+
{ id="CVE-2021-1675", cvss=9.8, sev="CRITICAL",
|
|
57
|
+
desc="PrintNightmare — Windows Print Spooler unauthenticated RCE via RpcAddPrinterDriverEx",
|
|
58
|
+
xpl="xpl/edb-50498" },
|
|
59
|
+
{ id="CVE-2021-34527", cvss=8.8, sev="HIGH",
|
|
60
|
+
desc="PrintNightmare (official) — patch bypass via RpcAddPrinterDriverEx with Point&Print",
|
|
61
|
+
xpl="xpl/edb-50498" },
|
|
62
|
+
{ id="CVE-2022-38028", cvss=7.8, sev="HIGH",
|
|
63
|
+
desc="GooseEgg — APT28 LPE via Windows Print Spooler (CISA KEV 2024, actively exploited)",
|
|
64
|
+
xpl="xpl/research/research-gooseegg-spooler" },
|
|
65
|
+
{ id="CVE-2020-1048", cvss=7.8, sev="HIGH",
|
|
66
|
+
desc="PrintDemon — persistent SYSTEM backdoor via printer port + DLL side-loading",
|
|
67
|
+
xpl="xpl/msf-cve-2020-1048-printerdemon" },
|
|
68
|
+
{ id="CVE-2020-1337", cvss=7.8, sev="HIGH",
|
|
69
|
+
desc="PrintDemon hardlink bypass — arbitrary privileged file write → SYSTEM",
|
|
70
|
+
xpl="xpl/edb-cve-2020-1337" },
|
|
71
|
+
{ id="CVE-2021-36958", cvss=7.8, sev="HIGH",
|
|
72
|
+
desc="Windows Print Spooler remote code execution (out-of-band patch)",
|
|
73
|
+
xpl="xpl/edb-50498" },
|
|
74
|
+
{ id="CVE-2022-21999", cvss=7.8, sev="HIGH",
|
|
75
|
+
desc="SpoolFool — Windows Print Spooler LPE via printer driver DLL planting",
|
|
76
|
+
xpl="xpl/edb-cve-2022-21999" },
|
|
77
|
+
{ id="CVE-2023-21678", cvss=7.8, sev="HIGH",
|
|
78
|
+
desc="Windows Print Spooler elevation of privilege (Jan 2023 Patch Tuesday)",
|
|
79
|
+
xpl="xpl/research/research-spooler-eop-2023" },
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
-- SMB negotiation probe to detect Windows
|
|
83
|
+
local function smb_probe(host, port)
|
|
84
|
+
if port.number ~= 445 and port.number ~= 139 then return nil end
|
|
85
|
+
local smb_neg =
|
|
86
|
+
"\x00\x00\x00\x2f" .. -- NetBIOS length
|
|
87
|
+
"\xff\x53\x4d\x42" .. -- SMB magic
|
|
88
|
+
"\x72\x00\x00\x00\x00\x08\x01\x40\x00\x00\x00\x00\x00\x00\x00\x00" ..
|
|
89
|
+
"\x00\x00\x00\x00\xff\xff\xff\xfe\x00\x00\x00\x00" ..
|
|
90
|
+
"\x00\x0c\x00\x02NT LM 0.12\x00"
|
|
91
|
+
|
|
92
|
+
local ok, resp = comm.exchange(host, port, smb_neg,
|
|
93
|
+
{ timeout = 5000, bytes = 256 })
|
|
94
|
+
if ok and resp then return resp end
|
|
95
|
+
return nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
-- RPC portmapper probe to check if spooler is registered
|
|
99
|
+
local function rpc_probe(host, port)
|
|
100
|
+
if port.number ~= 135 then return nil end
|
|
101
|
+
-- Minimal DCE/RPC bind to portmapper
|
|
102
|
+
local rpc_bind =
|
|
103
|
+
"\x05\x00\x0b\x03\x10\x00\x00\x00\x48\x00\x00\x00\x01\x00\x00\x00" ..
|
|
104
|
+
"\xd0\x16\xd0\x16\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00" ..
|
|
105
|
+
"\x08\x83\xaf\xe1\x1f\x5d\xc9\x11\x91\xa4\x08\x00\x2b\x14\xa0\xfa" ..
|
|
106
|
+
"\x03\x00\x00\x00\x33\x05\x71\x71\xba\xbe\x37\x49\x83\x19\xb5\xdb" ..
|
|
107
|
+
"\xef\x9c\xcc\x36\x01\x00\x00\x00"
|
|
108
|
+
local ok, resp = comm.exchange(host, port, rpc_bind,
|
|
109
|
+
{ timeout = 4000, bytes = 256 })
|
|
110
|
+
if ok and resp then return resp end
|
|
111
|
+
return nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
-- WSD (Web Services for Devices) probe on 3702/UDP
|
|
115
|
+
local function wsd_probe(host, port)
|
|
116
|
+
if port.number ~= 3702 then return nil end
|
|
117
|
+
local wsd_probe_xml =
|
|
118
|
+
'<?xml version="1.0" encoding="utf-8"?>' ..
|
|
119
|
+
'<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" ' ..
|
|
120
|
+
'xmlns:wsd="http://schemas.xmlsoap.org/ws/2005/04/discovery">' ..
|
|
121
|
+
'<soap:Body><wsd:Probe><wsd:Types>wsdp:Printer</wsd:Types></wsd:Probe></soap:Body>' ..
|
|
122
|
+
'</soap:Envelope>'
|
|
123
|
+
local ok, resp = comm.exchange(host, { number = 3702, protocol = "udp" },
|
|
124
|
+
wsd_probe_xml, { timeout = 3000, bytes = 1024 })
|
|
125
|
+
if ok then return resp end
|
|
126
|
+
return nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
action = function(host, port)
|
|
130
|
+
local out = stdnse.output_table()
|
|
131
|
+
local windows_detected = false
|
|
132
|
+
local smb_open = false
|
|
133
|
+
local rpc_open = false
|
|
134
|
+
local wsd_open = false
|
|
135
|
+
local os_hint = nil
|
|
136
|
+
|
|
137
|
+
-- SMB probe
|
|
138
|
+
if port.number == 445 or port.number == 139 then
|
|
139
|
+
local smb_resp = smb_probe(host, port)
|
|
140
|
+
if smb_resp then
|
|
141
|
+
smb_open = true
|
|
142
|
+
-- Check for Windows response marker
|
|
143
|
+
if smb_resp:match("\xff\x53\x4d\x42") then
|
|
144
|
+
windows_detected = true
|
|
145
|
+
-- Try to extract OS info
|
|
146
|
+
local os_str = smb_resp:match("Windows[%w%s%.]+")
|
|
147
|
+
if os_str then os_hint = os_str:sub(1, 50) end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
-- RPC probe
|
|
153
|
+
if port.number == 135 then
|
|
154
|
+
local rpc_resp = rpc_probe(host, port)
|
|
155
|
+
if rpc_resp then
|
|
156
|
+
rpc_open = true
|
|
157
|
+
windows_detected = true
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
-- WSD probe
|
|
162
|
+
if port.number == 3702 then
|
|
163
|
+
local wsd_resp = wsd_probe(host, port)
|
|
164
|
+
if wsd_resp then
|
|
165
|
+
wsd_open = true
|
|
166
|
+
if wsd_resp:lower():match("printer") then
|
|
167
|
+
windows_detected = true
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
if not smb_open and not rpc_open and not wsd_open then
|
|
173
|
+
return "No SMB/RPC/WSD response — Print Spooler not exposed or target is not Windows"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
-- Build output
|
|
177
|
+
if smb_open then out["SMB"] = "Open" end
|
|
178
|
+
if rpc_open then out["RPC"] = "Open (DCE/RPC portmapper responding)" end
|
|
179
|
+
if wsd_open then out["WSD"] = "Open (WSD printer broadcast)" end
|
|
180
|
+
if os_hint then out["OS"] = os_hint end
|
|
181
|
+
out["Windows"] = windows_detected and "Detected" or "Possible (SMB open, no OS banner)"
|
|
182
|
+
|
|
183
|
+
-- Spooler estimation
|
|
184
|
+
if smb_open and windows_detected then
|
|
185
|
+
out["Spooler-Status"] = "LIKELY RUNNING — SMB open on Windows host"
|
|
186
|
+
elseif smb_open then
|
|
187
|
+
out["Spooler-Status"] = "POSSIBLY RUNNING — SMB open, OS unconfirmed"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
-- CVE output
|
|
191
|
+
local cve_lines = {}
|
|
192
|
+
for _, c in ipairs(SPOOLER_CVES) do
|
|
193
|
+
table.insert(cve_lines, string.format("[%s] %s (CVSS %.1f) — %s\n module: %s",
|
|
194
|
+
c.sev, c.id, c.cvss, c.desc, c.xpl))
|
|
195
|
+
end
|
|
196
|
+
out["CVEs"] = cve_lines
|
|
197
|
+
|
|
198
|
+
-- Verdict
|
|
199
|
+
if windows_detected and smb_open then
|
|
200
|
+
out["Verdict"] = "POSSIBLY VULNERABLE — Windows + SMB confirmed; spooler status requires auth check"
|
|
201
|
+
out["Note"] = "Requires auth check: printerxpl-forge can enumerate spooler via NULL session or valid creds"
|
|
202
|
+
out["Suggest"] = "printerxpl-forge run --module xpl/edb-50498 --target " .. host.ip
|
|
203
|
+
else
|
|
204
|
+
out["Verdict"] = "POSSIBLY VULNERABLE — some exposure indicators present"
|
|
205
|
+
out["Suggest"] = "printerxpl-forge run --module xpl/edb-50498 --dry-run --target " .. host.ip
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
out["Full-Scan"] = "pip install printerxpl-forge | printerxpl-forge scan --target " .. host.ip
|
|
209
|
+
|
|
210
|
+
return out
|
|
211
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
local description = [[
|
|
2
|
+
printer-snmp-info — SNMP-based printer information extraction.
|
|
3
|
+
|
|
4
|
+
Queries standard Printer MIB (RFC 3805) and vendor-specific OIDs to retrieve:
|
|
5
|
+
device description, location, contact, serial number, firmware, page count,
|
|
6
|
+
ink/toner levels, and error log. Community strings tested: public, private,
|
|
7
|
+
internal, printer (common defaults).
|
|
8
|
+
|
|
9
|
+
CVE check: CVE-2022-1026 (Kyocera unauthenticated address book dump via SNMP).
|
|
10
|
+
|
|
11
|
+
Author: André Henrique (@mrhenrike) | União Geek
|
|
12
|
+
]]
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
-- @usage
|
|
16
|
+
-- nmap -sU -p 161 --script printer-snmp-info <target>
|
|
17
|
+
-- @output
|
|
18
|
+
-- PORT STATE SERVICE
|
|
19
|
+
-- 161/udp open snmp
|
|
20
|
+
-- | printer-snmp-info:
|
|
21
|
+
-- | sysDescr : HP LaserJet M606 FW FY5L.04
|
|
22
|
+
-- | sysLocation : Server Room B
|
|
23
|
+
-- | Serial : JPGD12345
|
|
24
|
+
-- | Page-Count : 127,493
|
|
25
|
+
-- | Community : public (readable — unauthenticated SNMP)
|
|
26
|
+
-- | Verdict : POSSIBLY VULNERABLE (public community writable?)
|
|
27
|
+
-- |_ Suggest : printerxpl-forge run --module xpl/edb-cve-2022-1026
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
categories = { "discovery", "safe", "vuln" }
|
|
31
|
+
author = "André Henrique (@mrhenrike) | União Geek"
|
|
32
|
+
license = "Same as Nmap -- See https://nmap.org/book/man-legal.html"
|
|
33
|
+
|
|
34
|
+
local stdnse = require "stdnse"
|
|
35
|
+
local shortport = require "shortport"
|
|
36
|
+
local snmp = require "snmp"
|
|
37
|
+
local comm = require "comm"
|
|
38
|
+
local string = require "string"
|
|
39
|
+
local table = require "table"
|
|
40
|
+
|
|
41
|
+
portrule = shortport.port_or_service(161, "snmp", "udp")
|
|
42
|
+
|
|
43
|
+
-- Standard + Printer MIB OIDs
|
|
44
|
+
local OIDS = {
|
|
45
|
+
{ oid = "1.3.6.1.2.1.1.1.0", key = "sysDescr" },
|
|
46
|
+
{ oid = "1.3.6.1.2.1.1.5.0", key = "sysName" },
|
|
47
|
+
{ oid = "1.3.6.1.2.1.1.6.0", key = "sysLocation" },
|
|
48
|
+
{ oid = "1.3.6.1.2.1.1.4.0", key = "sysContact" },
|
|
49
|
+
{ oid = "1.3.6.1.2.1.43.5.1.1.17.1", key = "Serial-Number" },
|
|
50
|
+
{ oid = "1.3.6.1.2.1.43.10.2.1.4.1.1", key = "Page-Count" },
|
|
51
|
+
{ oid = "1.3.6.1.2.1.43.11.1.1.9.1.1", key = "Toner-Level-%" },
|
|
52
|
+
{ oid = "1.3.6.1.4.1.11.2.3.9.1.1.7.0", key = "HP-Firmware" },
|
|
53
|
+
{ oid = "1.3.6.1.4.1.2435.2.3.9.4.2.1.5.5.8.0", key = "Brother-Model" },
|
|
54
|
+
{ oid = "1.3.6.1.4.1.18334.1.1.1.5.7.2.1.1.19.1", key = "Kyocera-Serial" },
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
local COMMUNITIES = { "public", "private", "internal", "printer", "snmpv1", "community", "admin" }
|
|
58
|
+
|
|
59
|
+
action = function(host, port)
|
|
60
|
+
local out = stdnse.output_table()
|
|
61
|
+
local community_used = nil
|
|
62
|
+
local results = {}
|
|
63
|
+
|
|
64
|
+
-- Try communities
|
|
65
|
+
for _, community in ipairs(COMMUNITIES) do
|
|
66
|
+
local req = snmp.buildPacket(snmp.PDU:new("GETNEXT", OIDS[1].oid), community)
|
|
67
|
+
local ok, resp = comm.exchange(host, port, req,
|
|
68
|
+
{ proto = "udp", timeout = 3000 })
|
|
69
|
+
if ok and resp and not resp:match("noSuchName") then
|
|
70
|
+
community_used = community
|
|
71
|
+
break
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if not community_used then
|
|
76
|
+
return "SNMP community not found — try: nmap -p 161 --script snmp-brute"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
out["Community"] = community_used .. " (readable)"
|
|
80
|
+
|
|
81
|
+
-- Walk OIDs with found community
|
|
82
|
+
for _, entry in ipairs(OIDS) do
|
|
83
|
+
local req = snmp.buildPacket(snmp.PDU:new("GET", entry.oid), community_used)
|
|
84
|
+
local ok, resp = comm.exchange(host, port, req,
|
|
85
|
+
{ proto = "udp", timeout = 2000 })
|
|
86
|
+
if ok and resp then
|
|
87
|
+
local val = snmp.parseResponse(resp)
|
|
88
|
+
if val and val ~= "" then
|
|
89
|
+
results[entry.key] = tostring(val):match("^%s*(.-)%s*$")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
for k, v in pairs(results) do
|
|
95
|
+
out[k] = v:sub(1, 120)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
-- Detect vendor from sysDescr
|
|
99
|
+
local desc_lower = (results["sysDescr"] or ""):lower()
|
|
100
|
+
local vendor = nil
|
|
101
|
+
if desc_lower:match("hp ") or desc_lower:match("laserjet") then
|
|
102
|
+
vendor = "HP"
|
|
103
|
+
elseif desc_lower:match("kyocera") then
|
|
104
|
+
vendor = "Kyocera"
|
|
105
|
+
elseif desc_lower:match("ricoh") or desc_lower:match("aficio") then
|
|
106
|
+
vendor = "Ricoh"
|
|
107
|
+
elseif desc_lower:match("lexmark") then
|
|
108
|
+
vendor = "Lexmark"
|
|
109
|
+
elseif desc_lower:match("canon") then
|
|
110
|
+
vendor = "Canon"
|
|
111
|
+
elseif desc_lower:match("xerox") then
|
|
112
|
+
vendor = "Xerox"
|
|
113
|
+
elseif desc_lower:match("brother") then
|
|
114
|
+
vendor = "Brother"
|
|
115
|
+
elseif desc_lower:match("konica") or desc_lower:match("bizhub") then
|
|
116
|
+
vendor = "Konica Minolta"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
if vendor then out["Vendor"] = vendor end
|
|
120
|
+
|
|
121
|
+
-- SNMP write test (COMMUNITY "private")
|
|
122
|
+
local write_possible = false
|
|
123
|
+
if community_used == "public" then
|
|
124
|
+
-- Try write with private
|
|
125
|
+
local write_req = snmp.buildPacket(
|
|
126
|
+
snmp.PDU:new("SET", "1.3.6.1.2.1.1.6.0", "nmap-test"), "private")
|
|
127
|
+
local wok, wresp = comm.exchange(host, port, write_req,
|
|
128
|
+
{ proto = "udp", timeout = 2000 })
|
|
129
|
+
if wok and wresp and not wresp:match("noAccess") and not wresp:match("readOnly") then
|
|
130
|
+
write_possible = true
|
|
131
|
+
out["SNMP-Write"] = "POSSIBLE with 'private' community — SNMP SET attacks feasible"
|
|
132
|
+
else
|
|
133
|
+
out["SNMP-Write"] = "Not available (read-only with public)"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
-- Kyocera CVE-2022-1026 check
|
|
138
|
+
local cves_found = {}
|
|
139
|
+
if vendor == "Kyocera" or (results["Kyocera-Serial"] ~= nil) then
|
|
140
|
+
table.insert(cves_found, {
|
|
141
|
+
id = "CVE-2022-1026",
|
|
142
|
+
sev = "HIGH",
|
|
143
|
+
cvss = 7.5,
|
|
144
|
+
desc = "Kyocera ECOSYS unauthenticated address book dump via SNMP/PJL",
|
|
145
|
+
xpl = "xpl/edb-cve-2022-1026",
|
|
146
|
+
})
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
-- Build verdict
|
|
150
|
+
local is_vuln = write_possible or #cves_found > 0
|
|
151
|
+
local is_possible = community_used == "public"
|
|
152
|
+
|
|
153
|
+
if #cves_found > 0 then
|
|
154
|
+
local cve_lines = {}
|
|
155
|
+
for _, c in ipairs(cves_found) do
|
|
156
|
+
table.insert(cve_lines, string.format("[%s] %s (CVSS %.1f) — %s | module: %s",
|
|
157
|
+
c.sev, c.id, c.cvss, c.desc, c.xpl))
|
|
158
|
+
end
|
|
159
|
+
out["CVEs"] = table.concat(cve_lines, " | ")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
if is_vuln then
|
|
163
|
+
out["Verdict"] = "POSSIBLY VULNERABLE — CVE match or SNMP write enabled"
|
|
164
|
+
if cves_found[1] then
|
|
165
|
+
out["Suggest"] = "printerxpl-forge run --module " .. cves_found[1].xpl .. " --target " .. host.ip
|
|
166
|
+
end
|
|
167
|
+
elseif is_possible then
|
|
168
|
+
out["Verdict"] = "POSSIBLY VULNERABLE — public community accessible (enumeration only)"
|
|
169
|
+
else
|
|
170
|
+
out["Verdict"] = "NOT VULNERABLE to detected CVEs (SNMP read-only, no CVE match)"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
out["Full-Scan"] = "pip install printerxpl-forge | printerxpl-forge scan --target " .. host.ip
|
|
174
|
+
|
|
175
|
+
return out
|
|
176
|
+
end
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
local description = [[
|
|
2
|
+
printer-vuln-check — Active, non-destructive vulnerability validation.
|
|
3
|
+
|
|
4
|
+
Performs lightweight active probes to confirm or rule out specific CVEs detected
|
|
5
|
+
by printer-cve-detect. Each check sends a minimal proof-of-concept payload and
|
|
6
|
+
evaluates the response — no damage, no file writes, no persistence.
|
|
7
|
+
|
|
8
|
+
Active checks included:
|
|
9
|
+
1. PJL NVRAM info-leak (DISPLAY/USTATUS abuse)
|
|
10
|
+
2. HP PostScript RCE probe (print error dict exploit path)
|
|
11
|
+
3. CUPS IPP newline injection marker (CVE-2026-34980)
|
|
12
|
+
4. Lexmark IPP malformed attr overflow indicator (CVE-2023-50739)
|
|
13
|
+
5. Canon SLP pre-auth probe (CVE-2022-24673)
|
|
14
|
+
6. Xerox command injection surface check (CVE-2021-27508/CVE-2024-6333)
|
|
15
|
+
7. Sharp unauthenticated command execution probe (CVE-2022-45796)
|
|
16
|
+
8. PrintNightmare SMB print spooler exposure check
|
|
17
|
+
|
|
18
|
+
Author: André Henrique (@mrhenrike) | União Geek
|
|
19
|
+
]]
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
-- @usage
|
|
23
|
+
-- nmap -p 9100,631,80,427,445 --script printer-vuln-check <target>
|
|
24
|
+
-- @output
|
|
25
|
+
-- PORT STATE SERVICE
|
|
26
|
+
-- 9100/tcp open jetdirect
|
|
27
|
+
-- | printer-vuln-check:
|
|
28
|
+
-- | [CHECK 1] PJL NVRAM info-leak : PASS (firmware info disclosed)
|
|
29
|
+
-- | [CHECK 2] HP PostScript RCE path : POSSIBLY VULNERABLE (error dict accessible)
|
|
30
|
+
-- | [CHECK 3] CUPS PPD injection : NOT VULNERABLE (CUPS not detected)
|
|
31
|
+
-- | Overall Verdict : POSSIBLY VULNERABLE
|
|
32
|
+
-- |_ Suggest : printerxpl-forge run --module xpl/edb-cve-2025-26506 --target <IP>
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
categories = { "vuln", "intrusive" }
|
|
36
|
+
author = "André Henrique (@mrhenrike) | União Geek"
|
|
37
|
+
license = "Same as Nmap -- See https://nmap.org/book/man-legal.html"
|
|
38
|
+
|
|
39
|
+
local stdnse = require "stdnse"
|
|
40
|
+
local shortport = require "shortport"
|
|
41
|
+
local comm = require "comm"
|
|
42
|
+
local http = require "http"
|
|
43
|
+
local string = require "string"
|
|
44
|
+
local table = require "table"
|
|
45
|
+
|
|
46
|
+
portrule = shortport.port_or_service(
|
|
47
|
+
{ 9100, 631, 80, 443, 427, 445, 515 },
|
|
48
|
+
{ "jetdirect", "ipp", "http", "https", "snmp", "printer" },
|
|
49
|
+
"tcp"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
local UEL = "\x1b%-12345X"
|
|
53
|
+
local V = { VULN = "VULNERABLE", POSS = "POSSIBLY VULNERABLE", NOT = "NOT VULNERABLE", SKIP = "SKIP" }
|
|
54
|
+
|
|
55
|
+
-- ── Check helpers ────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
local function check_pjl_nvram(host, port)
|
|
58
|
+
if port.number ~= 9100 then return V.SKIP, nil end
|
|
59
|
+
local cmd = UEL .. "@PJL INFO VARIABLES\r\nDISPLAY\r\n" .. UEL
|
|
60
|
+
local ok, r = comm.exchange(host, port, cmd, { timeout = 5000, bytes = 4096 })
|
|
61
|
+
if not ok then return V.SKIP, "no response" end
|
|
62
|
+
if r:match("VARIABLES") or r:match("DISPLAY") then
|
|
63
|
+
return V.POSS, "PJL VARIABLES/DISPLAY responded — NVRAM read-back possible"
|
|
64
|
+
end
|
|
65
|
+
return V.NOT, "PJL VARIABLES not readable"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
local function check_hp_ps_rce(host, port)
|
|
69
|
+
if port.number ~= 9100 then return V.SKIP, nil end
|
|
70
|
+
-- Minimal PS: push serverdict begin and check for response
|
|
71
|
+
local ps_probe = UEL ..
|
|
72
|
+
"@PJL ENTER LANGUAGE=POSTSCRIPT\r\n" ..
|
|
73
|
+
"serverdict begin 0 exitserver\r\n" ..
|
|
74
|
+
"systemdict begin\r\n" ..
|
|
75
|
+
"/internaldict where { pop internaldict /setpassword known { (probe-xpl) } if } if\r\n" ..
|
|
76
|
+
"flush\r\n" ..
|
|
77
|
+
UEL
|
|
78
|
+
local ok, r = comm.exchange(host, port, ps_probe, { timeout = 6000, bytes = 2048 })
|
|
79
|
+
if not ok then return V.SKIP, "no PS response" end
|
|
80
|
+
if r:match("probe.xpl") or r:match("internaldict") or r:match("setpassword") then
|
|
81
|
+
return V.VULN, "PostScript serverdict/exitserver accepted — RCE path confirmed"
|
|
82
|
+
elseif r:match("exitserver") or r:match("[Ee]rror") then
|
|
83
|
+
return V.POSS, "PS interpreter responded (exitserver/error) — partial RCE surface"
|
|
84
|
+
end
|
|
85
|
+
return V.NOT, "PS probe not reflected"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
local function check_cups_ppd_inject(host, port)
|
|
89
|
+
if port.number ~= 631 then return V.SKIP, nil end
|
|
90
|
+
local resp = http.get(host, port.number, "/", { timeout = 4000 })
|
|
91
|
+
if not resp or not resp.body then return V.SKIP, "IPP/HTTP not responding" end
|
|
92
|
+
local body = resp.body:lower()
|
|
93
|
+
if not body:match("cups") then return V.NOT, "CUPS not detected on port 631" end
|
|
94
|
+
local ver = resp.body:match("CUPS%s+([%d%.]+)")
|
|
95
|
+
if ver then
|
|
96
|
+
if ver == "2.4.16" then
|
|
97
|
+
return V.VULN, "CUPS 2.4.16 detected — CVE-2026-34980 unauthenticated RCE confirmed"
|
|
98
|
+
elseif ver >= "2.4.0" then
|
|
99
|
+
return V.POSS, "CUPS " .. ver .. " — possible CVE-2024-47176 (cups-browsed RCE chain)"
|
|
100
|
+
else
|
|
101
|
+
return V.NOT, "CUPS " .. ver .. " — not in vulnerable range"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
return V.POSS, "CUPS detected but version unknown — manual check recommended"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
local function check_lexmark_ipp_bof(host, port)
|
|
108
|
+
if port.number ~= 631 then return V.SKIP, nil end
|
|
109
|
+
-- Send IPP with oversized attribute name to test length validation
|
|
110
|
+
local big_attr = string.rep("A", 2048)
|
|
111
|
+
local payload =
|
|
112
|
+
"\x02\x00\x00\x0b\x00\x00\x00\x01\x01" ..
|
|
113
|
+
"\x47\x00\x12attributes-charset\x00\x05utf-8" ..
|
|
114
|
+
"\x48\x00\x1battributes-natural-language\x00\x05en-us" ..
|
|
115
|
+
"\x42" .. string.char(0x00, 0x08) .. "job-name" ..
|
|
116
|
+
string.char(0x08, 0x00) .. big_attr:sub(1, 2048) ..
|
|
117
|
+
"\x03"
|
|
118
|
+
local resp = http.post(
|
|
119
|
+
host, port.number, "/printers/",
|
|
120
|
+
{ header = { ["Content-Type"] = "application/ipp" }, timeout = 5000 },
|
|
121
|
+
nil, payload
|
|
122
|
+
)
|
|
123
|
+
if not resp then return V.SKIP, "no response" end
|
|
124
|
+
if resp.status == 500 or resp.status == 400 then
|
|
125
|
+
local body = (resp.body or ""):lower()
|
|
126
|
+
if body:match("lexmark") or body:match("ipp") then
|
|
127
|
+
return V.POSS, "Lexmark IPP returned " .. resp.status .. " on oversized attribute — CVE-2023-50739 surface"
|
|
128
|
+
end
|
|
129
|
+
elseif resp.status == 200 then
|
|
130
|
+
return V.NOT, "IPP accepted oversized attr without error (sanitized)"
|
|
131
|
+
end
|
|
132
|
+
return V.SKIP, "indeterminate response: HTTP " .. (resp.status or "nil")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
local function check_canon_slp(host, port)
|
|
136
|
+
if port.number ~= 427 then return V.SKIP, nil end
|
|
137
|
+
-- Minimal SLP service request for "printer" service type
|
|
138
|
+
local slp_req =
|
|
139
|
+
"\x02\x01" .. -- SLP v2, Service Request
|
|
140
|
+
"\x00\x00\x00\x30" ..
|
|
141
|
+
"\x00\x00\x00\x00\x00\x01" ..
|
|
142
|
+
"\x00\x05en\x00\x00" ..
|
|
143
|
+
"\x00\x07printer\x00\x00\x00"
|
|
144
|
+
local ok, r = comm.exchange(host, port, slp_req, { timeout = 4000, bytes = 512, proto = "udp" })
|
|
145
|
+
if not ok then
|
|
146
|
+
-- Try TCP SLP
|
|
147
|
+
ok, r = comm.exchange(host, port, slp_req, { timeout = 4000, bytes = 512 })
|
|
148
|
+
end
|
|
149
|
+
if ok and r then
|
|
150
|
+
local body = r:lower()
|
|
151
|
+
if body:match("canon") or body:match("imageclass") then
|
|
152
|
+
return V.VULN, "Canon SLP responding with device info — CVE-2022-24673 pre-auth BOF surface"
|
|
153
|
+
elseif #r > 4 then
|
|
154
|
+
return V.POSS, "SLP service responding on 427 — check for Canon imageCLASS (CVE-2022-24673)"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
return V.NOT, "SLP not responding on port 427"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
local function check_xerox_cmd_inject(host, port)
|
|
161
|
+
if port.number ~= 80 and port.number ~= 443 then return V.SKIP, nil end
|
|
162
|
+
-- Probe Xerox clone endpoint (CVE-2021-27508)
|
|
163
|
+
local r = http.get(host, port.number, "/cgi-bin/cloning", { timeout = 4000 })
|
|
164
|
+
if r then
|
|
165
|
+
if r.status == 200 then
|
|
166
|
+
return V.POSS, "Xerox /cgi-bin/cloning endpoint accessible (200) — CVE-2021-27508 surface"
|
|
167
|
+
elseif r.status == 302 or r.status == 401 then
|
|
168
|
+
return V.NOT, "Xerox cloning endpoint requires auth (HTTP " .. r.status .. ")"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
-- Probe VersaLink panel (CVE-2024-6333)
|
|
172
|
+
local r2 = http.get(host, port.number, "/web/index.html", { timeout = 3000 })
|
|
173
|
+
if r2 and r2.status == 200 and r2.body then
|
|
174
|
+
if r2.body:lower():match("versalink") then
|
|
175
|
+
return V.POSS, "Xerox VersaLink panel detected — CVE-2024-6333 requires authentication"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
return V.NOT, "Xerox endpoints not accessible"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
local function check_sharp_rce(host, port)
|
|
182
|
+
if port.number ~= 80 then return V.SKIP, nil end
|
|
183
|
+
local r = http.get(host, port.number, "/cgi-bin/system_cmd.cgi", { timeout = 4000 })
|
|
184
|
+
if r then
|
|
185
|
+
if r.status == 200 then
|
|
186
|
+
return V.VULN, "Sharp /cgi-bin/system_cmd.cgi accessible (200) — CVE-2022-45796 unauthenticated RCE"
|
|
187
|
+
elseif r.status == 401 or r.status == 403 then
|
|
188
|
+
return V.NOT, "Sharp cmd endpoint requires auth (HTTP " .. r.status .. ")"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
return V.NOT, "Sharp cmd endpoint not found"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
local function check_print_spooler(host, port)
|
|
195
|
+
if port.number ~= 445 then return V.SKIP, nil end
|
|
196
|
+
-- Check if port 445 is open (SMB) — PrintNightmare requires SMB + Spooler
|
|
197
|
+
-- We just fingerprint — actual exploit is in xpl/edb-50498
|
|
198
|
+
local ok, r = comm.exchange(host, port,
|
|
199
|
+
"\x00\x00\x00\x2f\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x08\x01\x40" ..
|
|
200
|
+
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xfe" ..
|
|
201
|
+
"\x00\x00\x00\x00\x00\x0c\x00\x02NT LM 0.12\x00",
|
|
202
|
+
{ timeout = 4000, bytes = 256 })
|
|
203
|
+
if ok and r and r:match("\xff\x53\x4d\x42") then
|
|
204
|
+
return V.POSS, "SMB port 445 open — PrintNightmare (CVE-2021-1675) requires Spooler + domain enum"
|
|
205
|
+
end
|
|
206
|
+
return V.NOT, "SMB not responding or not Windows"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
-- ── Main ─────────────────────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
action = function(host, port)
|
|
212
|
+
local out = stdnse.output_table()
|
|
213
|
+
local checks = {
|
|
214
|
+
{ name = "PJL NVRAM info-leak", fn = check_pjl_nvram, xpl = "xpl/edb-cve-2017-2741" },
|
|
215
|
+
{ name = "HP PostScript RCE path", fn = check_hp_ps_rce, xpl = "xpl/edb-cve-2025-26506" },
|
|
216
|
+
{ name = "CUPS PPD injection", fn = check_cups_ppd_inject, xpl = "xpl/research/research-cups-chain-2026" },
|
|
217
|
+
{ name = "Lexmark IPP BOF", fn = check_lexmark_ipp_bof, xpl = "xpl/edb-cve-2023-50739" },
|
|
218
|
+
{ name = "Canon SLP pre-auth BOF", fn = check_canon_slp, xpl = "xpl/edb-cve-2022-24673" },
|
|
219
|
+
{ name = "Xerox command injection surface", fn = check_xerox_cmd_inject, xpl = "xpl/edb-cve-2024-6333" },
|
|
220
|
+
{ name = "Sharp unauthenticated RCE", fn = check_sharp_rce, xpl = "xpl/research/research-sharp-rce" },
|
|
221
|
+
{ name = "PrintNightmare SMB exposure", fn = check_print_spooler, xpl = "xpl/edb-50498" },
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
local results = {}
|
|
225
|
+
local top_verdict = V.NOT
|
|
226
|
+
local top_xpl = nil
|
|
227
|
+
|
|
228
|
+
for i, chk in ipairs(checks) do
|
|
229
|
+
local verdict, detail = chk.fn(host, port)
|
|
230
|
+
local label = string.format("[CHECK %d] %-30s: %s", i, chk.name, verdict)
|
|
231
|
+
if detail and detail ~= "" then
|
|
232
|
+
label = label .. " (" .. detail .. ")"
|
|
233
|
+
end
|
|
234
|
+
table.insert(results, label)
|
|
235
|
+
|
|
236
|
+
-- Track overall verdict
|
|
237
|
+
if verdict == V.VULN and top_verdict ~= V.VULN then
|
|
238
|
+
top_verdict = V.VULN
|
|
239
|
+
top_xpl = chk.xpl
|
|
240
|
+
elseif verdict == V.POSS and top_verdict == V.NOT then
|
|
241
|
+
top_verdict = V.POSS
|
|
242
|
+
top_xpl = chk.xpl
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
out["Checks"] = results
|
|
247
|
+
out["Overall-Verdict"] = top_verdict
|
|
248
|
+
|
|
249
|
+
if top_xpl then
|
|
250
|
+
out["Suggest"] = "printerxpl-forge run --module " .. top_xpl .. " --target " .. host.ip
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
out["Full-Scan"] = "pip install printerxpl-forge | printerxpl-forge scan --target " .. host.ip
|
|
254
|
+
|
|
255
|
+
return out
|
|
256
|
+
end
|