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,217 @@
1
+ local description = [[
2
+ printer-banner — Multi-protocol printer banner grabber and vendor fingerprinting.
3
+
4
+ Probes ports 9100 (PJL/RAW), 631 (IPP), 80/443 (HTTP EWS), 515 (LPD), and 23 (Telnet)
5
+ to collect the printer's manufacturer, model, firmware version, and serial number.
6
+ Fingerprint results are cross-referenced against the PrinterXPL-Forge CVE database to
7
+ list potentially exploitable vulnerabilities and suggest specific exploit modules.
8
+
9
+ References:
10
+ https://github.com/mrhenrike/PrinterXPL-Forge
11
+ https://github.com/mrhenrike/PrinterXPL-Forge/wiki/NSE
12
+
13
+ Author: André Henrique (@mrhenrike) | União Geek
14
+ ]]
15
+
16
+ ---
17
+ -- @usage
18
+ -- nmap -sV -p 9100,631,80,443,515,23 --script printer-banner <target>
19
+ -- @output
20
+ -- PORT STATE SERVICE
21
+ -- 9100/tcp open jetdirect
22
+ -- | printer-banner:
23
+ -- | Vendor : HP
24
+ -- | Family : LaserJet/OfficeJet/PageWide
25
+ -- | Banner : HP LaserJet M606 / FutureSmart 5.6
26
+ -- | Firmware : FY5L.04.A.00.00
27
+ -- | Serial : JPGD12345
28
+ -- | CVEs : 3 matching (run printer-cve-detect for details)
29
+ -- |_ Suggest : printerxpl-forge run --module xpl/edb-cve-2025-26506 --target <IP>
30
+ --
31
+ -- @xmloutput
32
+ -- <table key="banner">...</table>
33
+ ---
34
+
35
+ categories = { "discovery", "safe", "default" }
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 nmap = require "nmap"
41
+ local shortport = require "shortport"
42
+ local comm = require "comm"
43
+ local http = require "http"
44
+ local string = require "string"
45
+ local table = require "table"
46
+
47
+ -- Load shared library (must be placed in nmap/nselib/ or nse/lib/ during install)
48
+ local ok, printerxpl = pcall(require, "printerxpl")
49
+ if not ok then
50
+ printerxpl = nil
51
+ end
52
+
53
+ -- ── Port rule: fire on any of the typical printer ports ──────────────────────
54
+ portrule = shortport.port_or_service(
55
+ { 9100, 631, 80, 443, 515, 23 },
56
+ { "jetdirect", "ipp", "http", "https", "printer", "telnet" },
57
+ "tcp"
58
+ )
59
+
60
+ -- ── PJL probe: send @PJL INFO ID and @PJL INFO CONFIG ────────────────────────
61
+ local function pjl_probe(host, port)
62
+ local uel = "\x1b%-12345X"
63
+ local cmd = uel .. "@PJL\r\n@PJL INFO ID\r\n@PJL INFO CONFIG\r\n@PJL INFO STATUS\r\n" .. uel
64
+ local status, result = comm.exchange(host, port, cmd, { timeout = 5000, bytes = 4096 })
65
+ if status then return result end
66
+ return nil
67
+ end
68
+
69
+ -- ── IPP probe: Get-Printer-Attributes (minimal) ───────────────────────────────
70
+ local function ipp_probe(host, port)
71
+ local resp = http.post(
72
+ host, port, "/ipp/print",
73
+ { header = { ["Content-Type"] = "application/ipp" } },
74
+ nil,
75
+ -- IPP Get-Printer-Attributes request (version 1.1)
76
+ "\x01\x01\x00\x0b\x00\x00\x00\x01" ..
77
+ "\x01" ..
78
+ "\x47\x00\x12attributes-charset\x00\x05utf-8" ..
79
+ "\x48\x00\x1battributes-natural-language\x00\x05en-us" ..
80
+ "\x03"
81
+ )
82
+ if resp and resp.status then
83
+ return resp.body or ""
84
+ end
85
+ return nil
86
+ end
87
+
88
+ -- ── HTTP EWS probe ───────────────────────────────────────────────────────────
89
+ local function http_probe(host, port)
90
+ local paths = { "/", "/hp/device/", "/cgi-bin/dynamic/", "/TopAccess/", "/web/index.html" }
91
+ for _, path in ipairs(paths) do
92
+ local resp = http.get(host, port, path, { timeout = 5000 })
93
+ if resp and resp.status == 200 and resp.body and #resp.body > 50 then
94
+ return resp.body
95
+ end
96
+ end
97
+ return nil
98
+ end
99
+
100
+ -- ── LPD probe: send LPD short-status request ─────────────────────────────────
101
+ local function lpd_probe(host, port)
102
+ local status, result = comm.exchange(host, port, "\x01\n", { timeout = 3000, bytes = 512 })
103
+ if status then return result end
104
+ return nil
105
+ end
106
+
107
+ -- ── Extract fields from combined banner text ──────────────────────────────────
108
+ local function extract_info(text)
109
+ local info = {}
110
+ -- Firmware / version
111
+ local fw = text:match("[Ff]irmware[Vv]ersion[^:]*:%s*([%w%.%-]+)")
112
+ or text:match("DATECODE=%s*([%w%.]+)")
113
+ or text:match("[Vv]ersion[^:]*:%s*([%w%.%-]+)")
114
+ if fw then info.firmware = fw end
115
+ -- Serial
116
+ local sn = text:match("[Ss]erial[Nn]umber[^:]*:%s*([%w%-]+)")
117
+ or text:match("SERIALNUMBER=%s*([%w%-]+)")
118
+ if sn then info.serial = sn end
119
+ -- Model (PJL style: DESCRIPTION="HP LaserJet P3015")
120
+ local model = text:match('[Dd][Ee][Ss][Cc][Rr][Ii][Pp][Tt][Ii][Oo][Nn]="([^"]+)"')
121
+ or text:match("[Mm]odel[^:]*:%s*([^\r\n]+)")
122
+ or text:match("[Pp]roduct:%s*([^\r\n]+)")
123
+ if model then info.model = model:match("^%s*(.-)%s*$") end
124
+ -- IP address (from CUPS / IPP response)
125
+ local ip = text:match("printer%-name%s*=%s*([%w%.%-]+)")
126
+ if ip then info.printer_name = ip end
127
+ return info
128
+ end
129
+
130
+ -- ── Main action ──────────────────────────────────────────────────────────────
131
+ action = function(host, port)
132
+ local portnum = port.number
133
+ local banner_text = ""
134
+ local source = ""
135
+
136
+ if portnum == 9100 then
137
+ local r = pjl_probe(host, port)
138
+ if r then banner_text = r; source = "PJL" end
139
+ elseif portnum == 631 then
140
+ local r = ipp_probe(host, port)
141
+ if r then banner_text = r; source = "IPP" end
142
+ elseif portnum == 80 or portnum == 443 then
143
+ local r = http_probe(host, port)
144
+ if r then banner_text = r; source = "HTTP-EWS" end
145
+ elseif portnum == 515 then
146
+ local r = lpd_probe(host, port)
147
+ if r then banner_text = r; source = "LPD" end
148
+ end
149
+
150
+ if banner_text == "" then
151
+ return nil
152
+ end
153
+
154
+ -- Vendor detection
155
+ local vendor, family = nil, nil
156
+ if printerxpl then
157
+ vendor, family = printerxpl.detect_vendor(banner_text)
158
+ else
159
+ -- Fallback inline detection
160
+ local b = banner_text:lower()
161
+ if b:match("hp") or b:match("hewlett") or b:match("laserjet") then
162
+ vendor, family = "HP", "LaserJet/OfficeJet"
163
+ elseif b:match("canon") then vendor, family = "Canon", "imageCLASS/imageRUNNER"
164
+ elseif b:match("lexmark") then vendor, family = "Lexmark", "MX/CX series"
165
+ elseif b:match("ricoh") or b:match("aficio") then vendor, family = "Ricoh", "Aficio/MP"
166
+ elseif b:match("xerox") then vendor, family = "Xerox", "WorkCentre/VersaLink"
167
+ elseif b:match("brother") then vendor, family = "Brother", "MFC/DCP"
168
+ elseif b:match("epson") then vendor, family = "Epson", "WorkForce"
169
+ elseif b:match("konica") or b:match("bizhub") then vendor, family = "Konica Minolta", "bizhub"
170
+ elseif b:match("kyocera") then vendor, family = "Kyocera", "ECOSYS"
171
+ elseif b:match("sharp") then vendor, family = "Sharp", "MX"
172
+ elseif b:match("toshiba") then vendor, family = "Toshiba", "e-STUDIO"
173
+ elseif b:match("samsung") then vendor, family = "Samsung", "CLX/SCX"
174
+ elseif b:match("cups") then vendor, family = "Linux/CUPS", "CUPS scheduler"
175
+ end
176
+ end
177
+
178
+ local info = extract_info(banner_text)
179
+
180
+ -- Collect CVE count hint
181
+ local cve_count = 0
182
+ local best_xpl = nil
183
+ if printerxpl and vendor then
184
+ local cves = printerxpl.match_cves(vendor)
185
+ cve_count = #cves
186
+ if cve_count > 0 then
187
+ best_xpl = cves[1].xpl
188
+ end
189
+ end
190
+
191
+ -- Build output
192
+ local out = stdnse.output_table()
193
+ out["Protocol"] = source
194
+ if vendor then
195
+ out["Vendor"] = vendor
196
+ out["Family"] = family or "Unknown"
197
+ end
198
+ if info.model then out["Model"] = info.model end
199
+ if info.firmware then out["Firmware"] = info.firmware end
200
+ if info.serial then out["Serial"] = info.serial end
201
+
202
+ -- Banner snippet (first 200 printable chars)
203
+ local snippet = banner_text:gsub("[%c]", " "):sub(1, 200):match("^%s*(.-)%s*$")
204
+ if #snippet > 0 then out["Banner"] = snippet end
205
+
206
+ if cve_count > 0 then
207
+ out["CVE-Count"] = string.format("%d matching CVEs (run printer-cve-detect for details)", cve_count)
208
+ out["Suggest"] = string.format(
209
+ "printerxpl-forge run --module %s --target %s", best_xpl, host.ip)
210
+ elseif vendor then
211
+ out["CVE-Count"] = "0 matching (vendor known but no specific CVE match on this port)"
212
+ end
213
+
214
+ out["Full-Scan"] = "pip install printerxpl-forge | printerxpl-forge scan --target " .. host.ip
215
+
216
+ return out
217
+ end
@@ -0,0 +1,189 @@
1
+ local description = [[
2
+ printer-cups-rce — CUPS vulnerability detection (CVE-2024-47176 / CVE-2026-34980).
3
+
4
+ Specifically targets CUPS schedulers to detect:
5
+ - CVE-2024-47176: cups-browsed listens on UDP 631 and accepts any browsing packet
6
+ that can redirect to an attacker-controlled IPP server → unauthenticated RCE
7
+ - CVE-2026-34980: CUPS 2.4.16 accepts newline characters in print job-name option,
8
+ allowing injection of PPD: FoomaticRIPCommandLine directives → unauthenticated RCE
9
+
10
+ Detection is passive + version-based; no actual exploit payload is sent.
11
+
12
+ Author: André Henrique (@mrhenrike) | União Geek
13
+ ]]
14
+
15
+ ---
16
+ -- @usage
17
+ -- nmap -sU -sT -p U:631,T:631 --script printer-cups-rce <target>
18
+ -- nmap -p 631 --script printer-cups-rce <target>
19
+ -- @output
20
+ -- PORT STATE SERVICE
21
+ -- 631/tcp open ipp
22
+ -- | printer-cups-rce:
23
+ -- | CUPS-Version : 2.4.16
24
+ -- | cups-browsed : DETECTED (listening on UDP 631)
25
+ -- | [CRITICAL] CVE-2026-34980 — CUPS 2.4.16 unauthenticated RCE via PPD injection
26
+ -- | [CRITICAL] CVE-2024-47176 — cups-browsed unauthenticated RCE chain
27
+ -- | Verdict : VULNERABLE
28
+ -- |_ Suggest : printerxpl-forge run --module xpl/research/research-cups-chain-2026
29
+ ---
30
+
31
+ categories = { "vuln", "safe" }
32
+ author = "André Henrique (@mrhenrike) | União Geek"
33
+ license = "Same as Nmap -- See https://nmap.org/book/man-legal.html"
34
+
35
+ local stdnse = require "stdnse"
36
+ local shortport = require "shortport"
37
+ local http = require "http"
38
+ local comm = require "comm"
39
+ local string = require "string"
40
+ local table = require "table"
41
+
42
+ portrule = shortport.port_or_service(631, { "ipp", "cups" }, { "tcp", "udp" })
43
+
44
+ local V = { VULN = "VULNERABLE", POSS = "POSSIBLY VULNERABLE", NOT = "NOT VULNERABLE" }
45
+
46
+ -- IPP Get-Printer-Attributes minimal request
47
+ local function ipp_probe(host, port)
48
+ local printer_uri = string.format("ipp://%s:%d/", host.ip, port.number)
49
+ local req =
50
+ "\x02\x00\x00\x0b\x00\x00\x00\x01\x01" ..
51
+ "\x47\x00\x12attributes-charset\x00\x05utf-8" ..
52
+ "\x48\x00\x1battributes-natural-language\x00\x05en-us" ..
53
+ "\x45\x00\x0bprinter-uri" ..
54
+ string.char(0x00, #printer_uri >> 8, #printer_uri & 0xff) .. printer_uri ..
55
+ "\x03"
56
+ local resp = http.post(
57
+ host, port.number, "/printers/",
58
+ { header = { ["Content-Type"] = "application/ipp" }, timeout = 6000 },
59
+ nil, req
60
+ )
61
+ if resp and resp.body then return resp.body end
62
+ return nil
63
+ end
64
+
65
+ -- cups-browsed UDP probe: send a CUPS browse packet to port 631 UDP
66
+ local function probe_cups_browsed(host, port)
67
+ -- Legacy CUPS browse packet (type=3 = printer, uri format)
68
+ local browse_pkt = "3 5 http://nmap-probe:631/printers/ ipp://nmap-probe/printers/default\n"
69
+ local ok, resp = comm.exchange(host, { number = 631, protocol = "udp" },
70
+ browse_pkt, { timeout = 3000, bytes = 256 })
71
+ -- If port sends any response or error, cups-browsed likely running
72
+ return ok
73
+ end
74
+
75
+ -- HTTP check on CUPS web interface
76
+ local function get_cups_web(host, port)
77
+ local r = http.get(host, port.number, "/", { timeout = 4000 })
78
+ if r and r.body then return r.body end
79
+ return nil
80
+ end
81
+
82
+ action = function(host, port)
83
+ local out = stdnse.output_table()
84
+
85
+ -- 1. IPP probe
86
+ local ipp_body = ipp_probe(host, port)
87
+
88
+ -- 2. HTTP CUPS web interface
89
+ local web_body = get_cups_web(host, port)
90
+
91
+ -- Combine for analysis
92
+ local combined = (ipp_body or "") .. (web_body or "")
93
+
94
+ if #combined < 10 then
95
+ return "No IPP/CUPS response on port " .. port.number
96
+ end
97
+
98
+ -- Check if CUPS
99
+ local is_cups = combined:lower():match("cups")
100
+ if not is_cups then
101
+ return "CUPS not detected on port " .. port.number .. " — try printer-ipp-info for generic IPP"
102
+ end
103
+
104
+ out["Service"] = "CUPS"
105
+
106
+ -- Extract version
107
+ local version = combined:match("CUPS%s+([%d%.]+)")
108
+ or combined:match("cups%-(%d+%.%d+%.%d+)")
109
+ or combined:match("<version>([%d%.]+)</version>")
110
+ if version then out["CUPS-Version"] = version end
111
+
112
+ -- cups-browsed probe
113
+ local browsed = probe_cups_browsed(host, port)
114
+ out["cups-browsed"] = browsed and "DETECTED (UDP 631 responding)" or "Not detected / filtered"
115
+
116
+ -- CVE determination
117
+ local cves = {}
118
+
119
+ if version == "2.4.16" then
120
+ table.insert(cves, {
121
+ id = "CVE-2026-34980",
122
+ sev = "CRITICAL",
123
+ cvss = 9.1,
124
+ desc = "CUPS 2.4.16 unauthenticated RCE via job-name newline → PPD FoomaticRIPCommandLine injection",
125
+ xpl = "xpl/research/research-cups-chain-2026",
126
+ })
127
+ table.insert(cves, {
128
+ id = "CVE-2026-34990",
129
+ sev = "HIGH",
130
+ cvss = 7.8,
131
+ desc = "CUPS 2.4.16 LPE to root via local printer race condition (chain with CVE-2026-34980)",
132
+ xpl = "xpl/research/research-cups-root-2026",
133
+ })
134
+ end
135
+
136
+ if browsed then
137
+ table.insert(cves, {
138
+ id = "CVE-2024-47176",
139
+ sev = "CRITICAL",
140
+ cvss = 9.9,
141
+ desc = "cups-browsed accepts any UDP browse packet → redirects print jobs to attacker IPP server → RCE chain",
142
+ xpl = "xpl/edb-cve-2024-47176",
143
+ })
144
+ table.insert(cves, {
145
+ id = "CVE-2024-47076",
146
+ sev = "HIGH",
147
+ cvss = 8.6,
148
+ desc = "libcupsfilters cfGetPrinterAttributes5 — no IPP attribute validation → data poisoning",
149
+ xpl = "xpl/edb-cve-2024-47176",
150
+ })
151
+ table.insert(cves, {
152
+ id = "CVE-2024-47175",
153
+ sev = "HIGH",
154
+ cvss = 8.6,
155
+ desc = "libppd ppdCreatePPDFromIPP2 — unsanitized IPP attrs written to PPD → code injection",
156
+ xpl = "xpl/edb-cve-2024-47176",
157
+ })
158
+ table.insert(cves, {
159
+ id = "CVE-2024-47177",
160
+ sev = "CRITICAL",
161
+ cvss = 9.9,
162
+ desc = "cups-filters FoomaticRIPCommandLine arbitrary command execution",
163
+ xpl = "xpl/edb-cve-2024-47176",
164
+ })
165
+ end
166
+
167
+ if #cves > 0 then
168
+ local lines = {}
169
+ for _, c in ipairs(cves) do
170
+ table.insert(lines, string.format("[%s] %s (CVSS %.1f) — %s",
171
+ c.sev, c.id, c.cvss, c.desc))
172
+ end
173
+ out["CVEs"] = lines
174
+
175
+ local is_vuln = version == "2.4.16" or browsed
176
+ if is_vuln then
177
+ out["Verdict"] = V.VULN .. " — " .. cves[1].id
178
+ else
179
+ out["Verdict"] = V.POSS .. " — check printer-vuln-check for confirmation"
180
+ end
181
+ out["Suggest"] = "printerxpl-forge run --module " .. cves[1].xpl .. " --target " .. host.ip
182
+ else
183
+ out["Verdict"] = V.NOT .. " — CUPS version not in vulnerable range and cups-browsed not detected"
184
+ end
185
+
186
+ out["Full-Scan"] = "pip install printerxpl-forge | printerxpl-forge run --module xpl/research/research-cups-chain-2026 --target " .. host.ip
187
+
188
+ return out
189
+ end
@@ -0,0 +1,279 @@
1
+ local description = [[
2
+ printer-cve-detect — Passive CVE detection from fingerprint data.
3
+
4
+ Aggregates banners collected by printer-banner, printer-pjl-info, printer-ipp-info,
5
+ and printer-http-ews scripts (via nmap registry) or performs its own lightweight
6
+ fingerprint if run standalone. Cross-references against the full PrinterXPL-Forge
7
+ CVE database (80+ CVEs) and outputs a ranked list of applicable vulnerabilities.
8
+
9
+ This script does NOT perform active exploitation — it is safe/non-intrusive.
10
+ For active validation use printer-vuln-check.
11
+
12
+ Author: André Henrique (@mrhenrike) | União Geek
13
+ ]]
14
+
15
+ ---
16
+ -- @usage
17
+ -- nmap -sV -p 9100,631,80 --script printer-banner,printer-cve-detect <target>
18
+ -- nmap -sV -p 9100,631,80 --script printer-cve-detect <target>
19
+ -- @output
20
+ -- PORT STATE SERVICE
21
+ -- 80/tcp open http
22
+ -- | printer-cve-detect:
23
+ -- | Vendor : Lexmark
24
+ -- | Model : CX622 (detected from HTTP EWS)
25
+ -- | CVEs (3 matching):
26
+ -- | [CRITICAL] CVE-2023-23560 CVSS 9.0 — Lexmark SSRF-to-RCE | module: xpl/edb-51928
27
+ -- | [HIGH] CVE-2023-50739 CVSS 8.8 — Lexmark IPP heap BOF | module: xpl/edb-cve-2023-50739
28
+ -- | [MEDIUM] CVE-2023-50733 CVSS 6.5 — Lexmark EWS SSRF | module: xpl/edb-cve-2023-50733
29
+ -- | Top Verdict : POSSIBLY VULNERABLE (CVE-2023-23560 — confirm with printer-vuln-check)
30
+ -- |_ Suggest : printerxpl-forge run --module xpl/edb-51928 --target <IP>
31
+ ---
32
+
33
+ categories = { "vuln", "safe", "discovery" }
34
+ author = "André Henrique (@mrhenrike) | União Geek"
35
+ license = "Same as Nmap -- See https://nmap.org/book/man-legal.html"
36
+
37
+ local stdnse = require "stdnse"
38
+ local shortport = require "shortport"
39
+ local http = require "http"
40
+ local comm = require "comm"
41
+ local string = require "string"
42
+ local table = require "table"
43
+
44
+ portrule = shortport.port_or_service(
45
+ { 9100, 631, 80, 443, 515, 161 },
46
+ { "jetdirect", "ipp", "http", "https", "printer", "snmp" },
47
+ { "tcp", "udp" }
48
+ )
49
+
50
+ -- Compact CVE database (VENDOR → list of CVEs)
51
+ local CVE_DB = {
52
+ HP = {
53
+ { id="CVE-2025-26506", cvss=9.8, sev="CRITICAL", port=9100,
54
+ desc="HP LaserJet Enterprise PostScript unauthenticated RCE",
55
+ xpl="xpl/edb-cve-2025-26506" },
56
+ { id="CVE-2023-6018", cvss=9.8, sev="CRITICAL", port=443,
57
+ desc="HP LaserJet Enterprise firmware auth bypass → arbitrary upload",
58
+ xpl="xpl/research/research-hp-fw-bypass" },
59
+ { id="CVE-2023-1707", cvss=9.1, sev="CRITICAL", port=443,
60
+ desc="HP FutureSmart 5.6 scan job data disclosure (IPsec enabled)",
61
+ xpl="xpl/research/research-hp-futuresmart-leak" },
62
+ { id="CVE-2017-2741", cvss=9.8, sev="CRITICAL", port=9100,
63
+ desc="HP PageWide PJL path traversal to RCE",
64
+ xpl="xpl/edb-cve-2017-2741" },
65
+ { id="CVE-2018-5924", cvss=9.8, sev="CRITICAL", port=9100,
66
+ desc="Faxploit — HP fax stack overflow via malformed fax data",
67
+ xpl="xpl/edb-cve-2018-5924" },
68
+ },
69
+ Canon = {
70
+ { id="CVE-2022-24673", cvss=9.8, sev="CRITICAL", port=427,
71
+ desc="Canon imageCLASS SLP pre-auth stack BOF → root RCE (ZDI-22-515)",
72
+ xpl="xpl/edb-cve-2022-24673" },
73
+ { id="CVE-2025-14235", cvss=9.8, sev="CRITICAL", port=9100,
74
+ desc="Canon imageCLASS PCL buffer overflow → RCE",
75
+ xpl="xpl/research/research-canon-pcl-bof" },
76
+ },
77
+ Lexmark = {
78
+ { id="CVE-2023-23560", cvss=9.0, sev="CRITICAL", port=80,
79
+ desc="Lexmark SSRF-to-RCE via Web Services interface (Pwn2Own 2022)",
80
+ xpl="xpl/edb-51928" },
81
+ { id="CVE-2023-26067", cvss=9.1, sev="CRITICAL", port=80,
82
+ desc="Lexmark post-auth RCE via device configuration API",
83
+ xpl="xpl/edb-cve-2023-26067" },
84
+ { id="CVE-2023-50739", cvss=8.8, sev="HIGH", port=631,
85
+ desc="Lexmark IPP heap buffer overflow → RCE (100+ models, ZDI-CAN-22549)",
86
+ xpl="xpl/edb-cve-2023-50739" },
87
+ { id="CVE-2023-50733", cvss=6.5, sev="MEDIUM", port=80,
88
+ desc="Lexmark EWS SSRF — printer as lateral movement pivot",
89
+ xpl="xpl/edb-cve-2023-50733" },
90
+ { id="CVE-2023-50738", cvss=7.2, sev="HIGH", port=80,
91
+ desc="Lexmark firmware downgrade bypass → re-expose historical vulns",
92
+ xpl="xpl/research/research-lexmark-fw-decrypt" },
93
+ },
94
+ Xerox = {
95
+ { id="CVE-2024-6333", cvss=8.1, sev="HIGH", port=80,
96
+ desc="Xerox VersaLink authenticated OS command injection",
97
+ xpl="xpl/edb-cve-2024-6333" },
98
+ { id="CVE-2021-27508", cvss=8.0, sev="HIGH", port=80,
99
+ desc="Xerox WorkCentre 78xx OS command injection via clone_group",
100
+ xpl="xpl/research/research-xerox-workcentre-cmdinject" },
101
+ },
102
+ Ricoh = {
103
+ { id="CVE-2024-34161", cvss=8.8, sev="HIGH", port=80,
104
+ desc="Ricoh MP EWS malformed HTTP → RCE",
105
+ xpl="xpl/research/research-ricoh-ews-rce" },
106
+ { id="CVE-2021-33945", cvss=7.5, sev="HIGH", port=80,
107
+ desc="Ricoh SP wpa_supplicant stack overflow → DoS/RCE",
108
+ xpl="xpl/research/research-ricoh-wpa-bof" },
109
+ },
110
+ Brother = {
111
+ { id="CVE-2024-51977", cvss=7.5, sev="HIGH", port=80,
112
+ desc="Brother admin password derivable from serial number (info disc.)",
113
+ xpl="xpl/research/research-brother-serial-pwd" },
114
+ { id="CVE-2024-51978", cvss=9.1, sev="CRITICAL", port=80,
115
+ desc="Brother auth bypass via serial-derived default credential",
116
+ xpl="xpl/research/research-brother-serial-pwd" },
117
+ },
118
+ Sharp = {
119
+ { id="CVE-2022-45796", cvss=9.8, sev="CRITICAL", port=80,
120
+ desc="Sharp MX unauthenticated OS command injection",
121
+ xpl="xpl/research/research-sharp-rce" },
122
+ },
123
+ Toshiba = {
124
+ { id="CVE-2024-21911", cvss=8.8, sev="HIGH", port=80,
125
+ desc="Toshiba e-STUDIO TopAccess authentication bypass → RCE",
126
+ xpl="xpl/research/research-toshiba-auth-bypass" },
127
+ },
128
+ Kyocera = {
129
+ { id="CVE-2022-1026", cvss=7.5, sev="HIGH", port=9100,
130
+ desc="Kyocera ECOSYS unauthenticated address book credential dump",
131
+ xpl="xpl/edb-cve-2022-1026" },
132
+ },
133
+ Microsoft = {
134
+ { id="CVE-2021-1675", cvss=9.8, sev="CRITICAL", port=445,
135
+ desc="PrintNightmare — Windows Print Spooler unauthenticated RCE/LPE",
136
+ xpl="xpl/edb-50498" },
137
+ { id="CVE-2022-38028", cvss=7.8, sev="HIGH", port=445,
138
+ desc="GooseEgg — APT28 Windows Print Spooler LPE (CISA KEV 2024)",
139
+ xpl="xpl/research/research-gooseegg-spooler" },
140
+ { id="CVE-2020-1048", cvss=7.8, sev="HIGH", port=445,
141
+ desc="PrintDemon — Windows Print Spooler persistent SYSTEM backdoor",
142
+ xpl="xpl/msf-cve-2020-1048-printerdemon" },
143
+ { id="CVE-2020-1337", cvss=7.8, sev="HIGH", port=445,
144
+ desc="PrintDemon hardlink bypass — arbitrary privileged file write",
145
+ xpl="xpl/edb-cve-2020-1337" },
146
+ },
147
+ ["Linux/CUPS"] = {
148
+ { id="CVE-2024-47176", cvss=9.9, sev="CRITICAL", port=631,
149
+ desc="CUPS cups-browsed unauthenticated RCE chain (2024)",
150
+ xpl="xpl/edb-cve-2024-47176" },
151
+ { id="CVE-2026-34980", cvss=9.1, sev="CRITICAL", port=631,
152
+ desc="CUPS 2.4.16 unauthenticated RCE via PPD newline injection (2026)",
153
+ xpl="xpl/research/research-cups-chain-2026" },
154
+ { id="CVE-2026-34990", cvss=7.8, sev="HIGH", port=631,
155
+ desc="CUPS 2.4.16 LPE to root via race condition on local printer",
156
+ xpl="xpl/research/research-cups-root-2026" },
157
+ },
158
+ }
159
+
160
+ -- Grab any banner we can get quickly
161
+ local function quick_fingerprint(host, port)
162
+ local portnum = port.number
163
+ local text = ""
164
+
165
+ if portnum == 9100 then
166
+ local uel = "\x1b%-12345X"
167
+ local ok, r = comm.exchange(host, port,
168
+ uel .. "@PJL\r\n@PJL INFO ID\r\n" .. uel,
169
+ { timeout = 4000, bytes = 2048 })
170
+ if ok then text = r end
171
+ elseif portnum == 80 or portnum == 443 then
172
+ local r = http.get(host, portnum, "/", { timeout = 4000 })
173
+ if r and r.body then text = r.body:sub(1, 2048) end
174
+ elseif portnum == 631 then
175
+ local r = http.get(host, portnum, "/", { timeout = 4000 })
176
+ if r and r.body then text = r.body:sub(1, 2048) end
177
+ end
178
+
179
+ return text
180
+ end
181
+
182
+ -- Detect vendor from text
183
+ local function detect_vendor(text)
184
+ if not text or text == "" then return nil end
185
+ local t = text:lower()
186
+ local checks = {
187
+ { pat = "hp laserjet|hewlett.packard|jetdirect|futuresmart", v = "HP" },
188
+ { pat = "canon imagerunner|canon imageclass|canon lbp", v = "Canon" },
189
+ { pat = "lexmark", v = "Lexmark" },
190
+ { pat = "xerox versalink|xerox workcentre|xerox altalink|xerox", v = "Xerox" },
191
+ { pat = "ricoh|aficio", v = "Ricoh" },
192
+ { pat = "brother mfc|brother dcp", v = "Brother" },
193
+ { pat = "epson workforce|epson ecotank", v = "Epson" },
194
+ { pat = "konica|bizhub", v = "Konica Minolta" },
195
+ { pat = "kyocera ecosys|kyocera taskalfa", v = "Kyocera" },
196
+ { pat = "sharp mx|sharp ar", v = "Sharp" },
197
+ { pat = "toshiba e.studio|topaccess", v = "Toshiba" },
198
+ { pat = "samsung scx|samsung clx", v = "Samsung" },
199
+ { pat = "cups 2%.4%.16", v = "Linux/CUPS" },
200
+ { pat = "cups", v = "Linux/CUPS" },
201
+ { pat = "windows.*spooler|print spooler|printdemon|spoolsv", v = "Microsoft" },
202
+ }
203
+ for _, c in ipairs(checks) do
204
+ if t:match(c.pat) then return c.v end
205
+ end
206
+ return nil
207
+ end
208
+
209
+ action = function(host, port)
210
+ -- Try registry first (from other printer scripts running in same session)
211
+ local banner = stdnse.registry_get({ host.ip, "printer-banner" }) or ""
212
+ local vendor = stdnse.registry_get({ host.ip, "printer-vendor" })
213
+
214
+ if not vendor or vendor == "" then
215
+ local fingerprint = quick_fingerprint(host, port)
216
+ banner = banner .. fingerprint
217
+ vendor = detect_vendor(fingerprint)
218
+ end
219
+
220
+ if not vendor then
221
+ return "Cannot determine vendor — combine with printer-banner: " ..
222
+ "nmap --script printer-banner,printer-cve-detect"
223
+ end
224
+
225
+ -- Store for other scripts
226
+ stdnse.registry_set({ host.ip, "printer-vendor" }, vendor)
227
+
228
+ local cve_list = CVE_DB[vendor] or {}
229
+ local out = stdnse.output_table()
230
+ out["Vendor"] = vendor
231
+
232
+ if #cve_list == 0 then
233
+ out["CVEs"] = "0 matching CVEs in database for vendor: " .. vendor
234
+ out["Verdict"] = "NOT VULNERABLE (no CVE entries for this vendor)"
235
+ return out
236
+ end
237
+
238
+ -- Filter by port relevance
239
+ local port_num = port.number
240
+ local relevant = {}
241
+ for _, cve in ipairs(cve_list) do
242
+ if (not cve.port) or cve.port == 0 or cve.port == port_num then
243
+ table.insert(relevant, cve)
244
+ else
245
+ -- Include high-severity regardless of port (printer may have other ports open)
246
+ if cve.cvss >= 9.0 then
247
+ table.insert(relevant, cve)
248
+ end
249
+ end
250
+ end
251
+
252
+ -- Sort by CVSS descending
253
+ table.sort(relevant, function(a, b) return a.cvss > b.cvss end)
254
+
255
+ out["CVE-Count"] = #relevant .. " matching CVEs for " .. vendor
256
+
257
+ local cve_lines = {}
258
+ for _, c in ipairs(relevant) do
259
+ table.insert(cve_lines, string.format(
260
+ "[%s] %s (CVSS %.1f) — %s\n module: printerxpl-forge run --module %s",
261
+ c.sev, c.id, c.cvss, c.desc, c.xpl))
262
+ end
263
+ out["CVEs"] = cve_lines
264
+
265
+ -- Verdict based on CVSS of top match
266
+ local top = relevant[1]
267
+ if top.cvss >= 9.0 then
268
+ out["Verdict"] = "POSSIBLY VULNERABLE — CRITICAL severity CVE match (confirm with printer-vuln-check)"
269
+ elseif top.cvss >= 7.0 then
270
+ out["Verdict"] = "POSSIBLY VULNERABLE — HIGH severity CVE match"
271
+ else
272
+ out["Verdict"] = "LOW RISK — only MEDIUM/LOW CVEs matched"
273
+ end
274
+
275
+ out["Suggest"] = "printerxpl-forge run --module " .. top.xpl .. " --dry-run --target " .. host.ip
276
+ out["Full-Scan"] = "pip install printerxpl-forge | printerxpl-forge scan --target " .. host.ip
277
+
278
+ return out
279
+ end