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,203 @@
1
+ local description = [[
2
+ printer-lexmark-ipp — Lexmark-specific IPP vulnerability assessment.
3
+
4
+ Targets Lexmark MFPs and printers via port 631 (IPP) and port 80 (EWS).
5
+ Detects and validates:
6
+ - CVE-2023-50739: IPP heap buffer overflow → RCE (100+ models, ZDI-CAN-22549)
7
+ - CVE-2023-23560: Lexmark Web Services SSRF → RCE (Pwn2Own Toronto 2022)
8
+ - CVE-2023-26067: Post-auth RCE via device configuration API
9
+ - CVE-2023-50733: EWS SSRF — printer as lateral movement pivot
10
+ - CVE-2023-50738: Firmware downgrade bypass
11
+ - CVE-2022-24935: Lexmark authentication bypass
12
+
13
+ Author: André Henrique (@mrhenrike) | União Geek
14
+ ]]
15
+
16
+ ---
17
+ -- @usage
18
+ -- nmap -p 631,80 --script printer-lexmark-ipp <target>
19
+ -- @output
20
+ -- PORT STATE SERVICE
21
+ -- 631/tcp open ipp
22
+ -- | printer-lexmark-ipp:
23
+ -- | Vendor : Lexmark
24
+ -- | Model : CX622 (detected from IPP attributes)
25
+ -- | [CRITICAL] CVE-2023-23560 (CVSS 9.0) — Lexmark SSRF-to-RCE
26
+ -- | [HIGH] CVE-2023-50739 (CVSS 8.8) — Lexmark IPP heap BOF (100+ models)
27
+ -- | [MEDIUM] CVE-2023-50733 (CVSS 6.5) — Lexmark EWS SSRF pivot
28
+ -- | Verdict : POSSIBLY VULNERABLE
29
+ -- |_ Suggest : printerxpl-forge run --module xpl/edb-51928 --target <IP>
30
+ ---
31
+
32
+ categories = { "vuln", "safe" }
33
+ author = "André Henrique (@mrhenrike) | União Geek"
34
+ license = "Same as Nmap -- See https://nmap.org/book/man-legal.html"
35
+
36
+ local stdnse = require "stdnse"
37
+ local shortport = require "shortport"
38
+ local http = require "http"
39
+ local string = require "string"
40
+ local table = require "table"
41
+
42
+ portrule = shortport.port_or_service({ 631, 80, 443 }, { "ipp", "http", "https" }, "tcp")
43
+
44
+ local LEXMARK_CVES = {
45
+ { id="CVE-2023-23560", cvss=9.0, sev="CRITICAL",
46
+ desc="Lexmark SSRF-to-RCE via Web Services interface — unauthenticated (Pwn2Own Toronto 2022)",
47
+ xpl="xpl/edb-51928",
48
+ port_check=80 },
49
+ { id="CVE-2023-26067", cvss=9.1, sev="CRITICAL",
50
+ desc="Lexmark post-auth RCE via device configuration API",
51
+ xpl="xpl/edb-cve-2023-26067",
52
+ port_check=80 },
53
+ { id="CVE-2023-50739", cvss=8.8, sev="HIGH",
54
+ desc="Lexmark IPP heap buffer overflow → RCE (100+ models, ZDI-CAN-22549)",
55
+ xpl="xpl/edb-cve-2023-50739",
56
+ port_check=631 },
57
+ { id="CVE-2023-50733", cvss=6.5, sev="MEDIUM",
58
+ desc="Lexmark EWS SSRF — pivot to internal network resources",
59
+ xpl="xpl/edb-cve-2023-50733",
60
+ port_check=80 },
61
+ { id="CVE-2023-50738", cvss=7.2, sev="HIGH",
62
+ desc="Lexmark firmware downgrade bypass → re-expose historical vulnerabilities",
63
+ xpl="xpl/research/research-lexmark-fw-decrypt",
64
+ port_check=80 },
65
+ { id="CVE-2022-24935", cvss=8.0, sev="HIGH",
66
+ desc="Lexmark authentication bypass via crafted POST to NPS endpoint",
67
+ xpl="xpl/research/research-lexmark-auth",
68
+ port_check=80 },
69
+ }
70
+
71
+ -- IPP probe to detect Lexmark
72
+ local function ipp_fingerprint(host, port)
73
+ if port.number ~= 631 then return nil end
74
+ local req =
75
+ "\x02\x00\x00\x0b\x00\x00\x00\x01\x01" ..
76
+ "\x47\x00\x12attributes-charset\x00\x05utf-8" ..
77
+ "\x48\x00\x1battributes-natural-language\x00\x05en-us" ..
78
+ "\x45\x00\x0bprinter-uri\x00\x0cipp://0.0.0.0/" ..
79
+ "\x03"
80
+ local resp = http.post(host, 631, "/printers/",
81
+ { header = { ["Content-Type"] = "application/ipp" }, timeout = 5000 },
82
+ nil, req)
83
+ if resp and resp.body then return resp.body end
84
+ return nil
85
+ end
86
+
87
+ -- HTTP EWS probe to detect Lexmark
88
+ local function http_fingerprint(host, port)
89
+ local paths = { "/", "/cgi-bin/dynamic/", "/lexmark/", "/printer.html" }
90
+ for _, p in ipairs(paths) do
91
+ local r = http.get(host, port.number, p, { timeout = 5000 })
92
+ if r and r.status == 200 and r.body then
93
+ if r.body:lower():match("lexmark") then
94
+ return r.body
95
+ end
96
+ end
97
+ end
98
+ return nil
99
+ end
100
+
101
+ -- Test CVE-2023-23560 SSRF surface
102
+ local function check_ssrf_surface(host, port)
103
+ local r = http.get(host, port.number, "/cgi-bin/dynamic/config/webservices.html",
104
+ { timeout = 4000 })
105
+ if r and (r.status == 200 or r.status == 302) then
106
+ return true
107
+ end
108
+ -- Try alternate
109
+ r = http.get(host, port.number, "/webservices", { timeout = 3000 })
110
+ if r and (r.status == 200 or r.status == 302) then return true end
111
+ return false
112
+ end
113
+
114
+ -- Test CVE-2022-24935 auth bypass surface
115
+ local function check_nps_bypass(host, port)
116
+ local r = http.post(host, port.number, "/cgi-bin/dynamic/nps",
117
+ { header = { ["Content-Type"] = "application/x-www-form-urlencoded" },
118
+ timeout = 4000 },
119
+ nil, "auth=probe")
120
+ if r and r.status == 200 then
121
+ if r.body and r.body:lower():match("lexmark") then
122
+ return true
123
+ end
124
+ end
125
+ return false
126
+ end
127
+
128
+ action = function(host, port)
129
+ local out = stdnse.output_table()
130
+
131
+ -- Fingerprint
132
+ local banner = nil
133
+ local is_lexmark = false
134
+
135
+ if port.number == 631 then
136
+ banner = ipp_fingerprint(host, port)
137
+ if banner and banner:lower():match("lexmark") then is_lexmark = true end
138
+ end
139
+
140
+ if not is_lexmark then
141
+ banner = http_fingerprint(host, port)
142
+ if banner then is_lexmark = true end
143
+ end
144
+
145
+ if not is_lexmark then
146
+ return "Lexmark not detected on port " .. port.number ..
147
+ " — use printer-cve-detect for generic CVE matching"
148
+ end
149
+
150
+ out["Vendor"] = "Lexmark"
151
+
152
+ -- Extract model if possible
153
+ if banner then
154
+ local model = banner:match("[Ll]exmark%s+([%w%-]+%s*[%w%-]*)")
155
+ if model then out["Model"] = model:match("^%s*(.-)%s*$"):sub(1, 60) end
156
+ end
157
+
158
+ -- Active surface checks
159
+ local ssrf_exposed = check_ssrf_surface(host, port)
160
+ local nps_exposed = check_nps_bypass(host, port)
161
+
162
+ if ssrf_exposed then out["SSRF-Surface"] = "Web Services config accessible — CVE-2023-23560" end
163
+ if nps_exposed then out["NPS-Surface"] = "NPS endpoint responding — CVE-2022-24935" end
164
+
165
+ -- Match CVEs
166
+ local matched = {}
167
+ for _, cve in ipairs(LEXMARK_CVES) do
168
+ local relevant = (not cve.port_check) or (cve.port_check == port.number)
169
+ if not relevant then
170
+ relevant = cve.cvss >= 9.0 -- always include critical
171
+ end
172
+ local triggered = relevant
173
+ if cve.id == "CVE-2023-23560" and not ssrf_exposed then triggered = relevant end
174
+ if cve.id == "CVE-2022-24935" and not nps_exposed then triggered = relevant end
175
+ if triggered then table.insert(matched, cve) end
176
+ end
177
+
178
+ -- Sort by CVSS
179
+ table.sort(matched, function(a, b) return a.cvss > b.cvss end)
180
+
181
+ if #matched > 0 then
182
+ local cve_lines = {}
183
+ for _, c in ipairs(matched) do
184
+ table.insert(cve_lines, string.format("[%s] %s (CVSS %.1f) — %s\n module: %s",
185
+ c.sev, c.id, c.cvss, c.desc, c.xpl))
186
+ end
187
+ out["CVEs"] = cve_lines
188
+ end
189
+
190
+ -- Verdict
191
+ local is_vuln = ssrf_exposed or nps_exposed
192
+ if is_vuln then
193
+ out["Verdict"] = "POSSIBLY VULNERABLE — active surface exposure confirmed"
194
+ out["Suggest"] = "printerxpl-forge run --module " .. matched[1].xpl .. " --target " .. host.ip
195
+ else
196
+ out["Verdict"] = "POSSIBLY VULNERABLE — Lexmark confirmed but active surfaces not exposed"
197
+ out["Suggest"] = "printerxpl-forge run --module " .. matched[1].xpl .. " --dry-run --target " .. host.ip
198
+ end
199
+
200
+ out["Full-Scan"] = "pip install printerxpl-forge | printerxpl-forge scan --target " .. host.ip
201
+
202
+ return out
203
+ end
@@ -0,0 +1,204 @@
1
+ local description = [[
2
+ printer-passback — LDAP/SMB credential passback attack surface detection.
3
+
4
+ Modern multifunction printers (MFPs) support scan-to-email, scan-to-folder
5
+ (SMB), LDAP authentication, and network scanning. If these features are
6
+ configured, a rogue LDAP/SMB server can capture domain credentials when the
7
+ printer initiates a connection ("passback attack").
8
+
9
+ This script detects:
10
+ - Accessible LDAP configuration pages (scan-to-email / address book LDAP)
11
+ - SMB scan destination configuration pages
12
+ - Network credential test endpoints
13
+ - Active Directory integration surfaces
14
+
15
+ CVE coverage:
16
+ - CVE-2022-23968: Xerox FutureSmart LDAP passback
17
+ - CVE-2022-23969: Xerox FutureSmart SMB passback
18
+ - Generic passback surface detection for HP, Ricoh, Konica, Canon, etc.
19
+
20
+ Author: André Henrique (@mrhenrike) | União Geek
21
+ ]]
22
+
23
+ ---
24
+ -- @usage
25
+ -- nmap -p 80,443 --script printer-passback <target>
26
+ -- @output
27
+ -- PORT STATE SERVICE
28
+ -- 80/tcp open http
29
+ -- | printer-passback:
30
+ -- | LDAP-Config : /hp/device/LdapConfiguration.htm (HTTP 200 — accessible)
31
+ -- | SMB-Config : /hp/device/SmbConfiguration.htm (HTTP 200 — accessible)
32
+ -- | [HIGH] CVE-2022-23968 — Xerox LDAP passback → domain credential capture
33
+ -- | [HIGH] CVE-2022-23969 — Xerox SMB passback → domain credential capture
34
+ -- | Verdict : POSSIBLY VULNERABLE — LDAP/SMB config pages accessible
35
+ -- |_ Suggest : printerxpl-forge run --module xpl/research/research-passback-ldap
36
+ ---
37
+
38
+ categories = { "vuln", "safe", "discovery" }
39
+ author = "André Henrique (@mrhenrike) | União Geek"
40
+ license = "Same as Nmap -- See https://nmap.org/book/man-legal.html"
41
+
42
+ local stdnse = require "stdnse"
43
+ local shortport = require "shortport"
44
+ local http = require "http"
45
+ local string = require "string"
46
+ local table = require "table"
47
+
48
+ portrule = shortport.http
49
+
50
+ -- Passback surface endpoints per vendor
51
+ local PASSBACK_ENDPOINTS = {
52
+ -- HP
53
+ { path="/hp/device/LdapConfiguration.htm", vendor="HP", proto="LDAP",
54
+ cve="CVE-2022-23968-HP", desc="HP EWS LDAP directory config" },
55
+ { path="/hp/device/NetworkFolderConfiguration.htm", vendor="HP", proto="SMB",
56
+ cve="CVE-2022-23969-HP", desc="HP EWS SMB scan-to-folder config" },
57
+ { path="/hp/device/EmailConfiguration.htm", vendor="HP", proto="SMTP",
58
+ cve=nil, desc="HP EWS email config (SMTP credentials)" },
59
+ -- Xerox
60
+ { path="/cgi-bin/dynamic/userprefs/LdapConfiguration.html", vendor="Xerox", proto="LDAP",
61
+ cve="CVE-2022-23968", desc="Xerox FutureSmart LDAP config (CISA alert)" },
62
+ { path="/cgi-bin/dynamic/userprefs/SmtpConfiguration.html", vendor="Xerox", proto="SMTP",
63
+ cve=nil, desc="Xerox SMTP config (email credentials)" },
64
+ { path="/cgi-bin/dynamic/userprefs/SmbConfiguration.html", vendor="Xerox", proto="SMB",
65
+ cve="CVE-2022-23969", desc="Xerox FutureSmart SMB config (CISA alert)" },
66
+ { path="/web/entry.cgi?id=ldap", vendor="Ricoh", proto="LDAP",
67
+ cve=nil, desc="Ricoh LDAP config" },
68
+ { path="/web/entry.cgi?id=scan_folder", vendor="Ricoh", proto="SMB",
69
+ cve=nil, desc="Ricoh scan-to-folder SMB config" },
70
+ -- Konica
71
+ { path="/wcd/ldap.html", vendor="Konica", proto="LDAP",
72
+ cve=nil, desc="Konica bizhub LDAP config" },
73
+ { path="/wcd/smb_setting.html", vendor="Konica", proto="SMB",
74
+ cve=nil, desc="Konica bizhub SMB scan config" },
75
+ -- Toshiba
76
+ { path="/TopAccess/Admin/Setting/E-MAIL/LDAPSetting.htm", vendor="Toshiba", proto="LDAP",
77
+ cve=nil, desc="Toshiba TopAccess LDAP auth config" },
78
+ -- Canon
79
+ { path="/cgi-bin/dynamicconfig", vendor="Canon", proto="LDAP",
80
+ cve=nil, desc="Canon dynamic config (may include LDAP)" },
81
+ -- Brother
82
+ { path="/network/network.html", vendor="Brother", proto="SMTP",
83
+ cve=nil, desc="Brother network config (SMTP credentials)" },
84
+ -- Kyocera
85
+ { path="/js/jssrc/model/system/LdapSetting.js", vendor="Kyocera", proto="LDAP",
86
+ cve=nil, desc="Kyocera LDAP setting JS (check for config exposure)" },
87
+ -- Generic
88
+ { path="/cgi-bin/dynamic/config/ldapconfig.html", vendor="Generic", proto="LDAP",
89
+ cve=nil, desc="Generic LDAP config endpoint" },
90
+ { path="/ldap", vendor="Generic", proto="LDAP",
91
+ cve=nil, desc="Generic /ldap path" },
92
+ { path="/scan/smb", vendor="Generic", proto="SMB",
93
+ cve=nil, desc="Generic scan-to-SMB endpoint" },
94
+ }
95
+
96
+ -- Known passback CVEs for detailed output
97
+ local PASSBACK_CVES = {
98
+ ["CVE-2022-23968"] = {
99
+ sev="HIGH", cvss=7.5,
100
+ desc="Xerox FutureSmart LDAP passback — printer sends LDAP bind to rogue server capturing domain creds",
101
+ xpl="xpl/research/research-passback-ldap",
102
+ },
103
+ ["CVE-2022-23969"] = {
104
+ sev="HIGH", cvss=7.5,
105
+ desc="Xerox FutureSmart SMB passback — printer authenticates to rogue SMB share capturing NTLM hashes",
106
+ xpl="xpl/research/research-passback-smb",
107
+ },
108
+ }
109
+
110
+ action = function(host, port)
111
+ local out = stdnse.output_table()
112
+
113
+ -- Quick root check
114
+ local root = http.get(host, port.number, "/", { timeout = 4000 })
115
+ if not root then return "HTTP not responding" end
116
+
117
+ local accessible = {}
118
+ local cves_triggered = {}
119
+ local seen = {}
120
+ local protos_found = {}
121
+
122
+ for _, ep in ipairs(PASSBACK_ENDPOINTS) do
123
+ local resp = http.get(host, port.number, ep.path,
124
+ { timeout = 4000, redirect_ok = false })
125
+ if resp and (resp.status == 200 or resp.status == 302) then
126
+ local entry = string.format("%s (%s, HTTP %d) [%s — %s]",
127
+ ep.path, ep.proto, resp.status, ep.vendor, ep.desc)
128
+ table.insert(accessible, entry)
129
+ protos_found[ep.proto] = true
130
+
131
+ if ep.cve and not seen[ep.cve] then
132
+ seen[ep.cve] = true
133
+ local cve_detail = PASSBACK_CVES[ep.cve]
134
+ if cve_detail then
135
+ table.insert(cves_triggered, {
136
+ id = ep.cve,
137
+ sev = cve_detail.sev,
138
+ cvss = cve_detail.cvss,
139
+ desc = cve_detail.desc,
140
+ xpl = cve_detail.xpl,
141
+ })
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ if #accessible == 0 then
148
+ return "No LDAP/SMB passback surfaces detected (admin pages may require auth or non-standard paths)"
149
+ end
150
+
151
+ -- Protocol summary
152
+ local proto_list = {}
153
+ for p, _ in pairs(protos_found) do table.insert(proto_list, p) end
154
+ out["Protocols"] = table.concat(proto_list, ", ")
155
+
156
+ out["Config-Pages-Found"] = accessible
157
+
158
+ -- CVE output
159
+ if #cves_triggered > 0 then
160
+ local cve_lines = {}
161
+ for _, c in ipairs(cves_triggered) do
162
+ table.insert(cve_lines, string.format("[%s] %s (CVSS %.1f) — %s",
163
+ c.sev, c.id, c.cvss, c.desc))
164
+ end
165
+ out["CVEs"] = cve_lines
166
+ end
167
+
168
+ -- Passback explanation
169
+ local has_ldap = protos_found["LDAP"]
170
+ local has_smb = protos_found["SMB"]
171
+
172
+ local attack_desc = {}
173
+ if has_ldap then
174
+ table.insert(attack_desc,
175
+ "LDAP passback: redirect printer LDAP queries to rogue server → capture domain credentials")
176
+ end
177
+ if has_smb then
178
+ table.insert(attack_desc,
179
+ "SMB passback: redirect printer SMB auth to Responder/rogue share → capture NTLM hashes")
180
+ end
181
+ if #attack_desc > 0 then
182
+ out["Attack-Vector"] = attack_desc
183
+ end
184
+
185
+ -- Verdict
186
+ out["Verdict"] = "POSSIBLY VULNERABLE — " ..
187
+ #accessible .. " config page(s) accessible; passback attack feasible if admin credentials known"
188
+
189
+ if #cves_triggered > 0 then
190
+ out["Suggest"] = "printerxpl-forge run --module " .. cves_triggered[1].xpl .. " --target " .. host.ip
191
+ else
192
+ out["Suggest"] = "printerxpl-forge run --module xpl/research/research-passback-ldap --target " .. host.ip
193
+ end
194
+
195
+ out["Manual-Steps"] =
196
+ "1. Set up rogue LDAP/SMB server (Responder or custom) " ..
197
+ "2. Modify printer LDAP/SMB server IP to attacker IP " ..
198
+ "3. Trigger auth test from printer web UI " ..
199
+ "4. Capture credentials on rogue server"
200
+
201
+ out["Full-Scan"] = "pip install printerxpl-forge | printerxpl-forge scan --target " .. host.ip
202
+
203
+ return out
204
+ end
@@ -0,0 +1,146 @@
1
+ local description = [[
2
+ printer-pjl-info — Deep PJL enumeration via port 9100.
3
+
4
+ Sends multiple @PJL INFO commands to extract: device identity (ID, MODEL),
5
+ environment variables (TIMEOUT, RESOLUTION, COPIES), filesystem listing (FSLIST),
6
+ status (JOB status, READY state), and network configuration.
7
+
8
+ Also tests for PJL password protection — unprotected devices are flagged.
9
+
10
+ References:
11
+ https://github.com/mrhenrike/PrinterXPL-Forge
12
+ https://h20195.www2.hp.com/v2/GetDocument.aspx?docname=bpl13208 (HP PJL Tech Ref)
13
+
14
+ Author: André Henrique (@mrhenrike) | União Geek
15
+ ]]
16
+
17
+ ---
18
+ -- @usage
19
+ -- nmap -p 9100 --script printer-pjl-info <target>
20
+ -- @output
21
+ -- PORT STATE SERVICE
22
+ -- 9100/tcp open jetdirect
23
+ -- | printer-pjl-info:
24
+ -- | ID : HP LASERJET M606
25
+ -- | Model : HP LaserJet Enterprise M606dn
26
+ -- | Firmware : FY5L.04.A.00.00
27
+ -- | Serial : JPGD12345
28
+ -- | Resolution : 1200
29
+ -- | Timeout : 15
30
+ -- | Filesystem : 0:\ [HDD 40GB]
31
+ -- | PJL-Password : NOT SET (unprotected — PJL job injection possible)
32
+ -- |_ Verdict : POSSIBLY VULNERABLE (unrestricted PJL access)
33
+ ---
34
+
35
+ categories = { "discovery", "safe", "vuln" }
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 string = require "string"
43
+
44
+ portrule = shortport.port_or_service(9100, "jetdirect", "tcp")
45
+
46
+ local UEL = "\x1b%-12345X"
47
+
48
+ local PJL_QUERIES = {
49
+ { cmd = "@PJL INFO ID\r\n", key = "ID" },
50
+ { cmd = "@PJL INFO CONFIG\r\n", key = "Config" },
51
+ { cmd = "@PJL INFO VARIABLES\r\n", key = "Variables" },
52
+ { cmd = "@PJL INFO FILESYS\r\n", key = "Filesystem" },
53
+ { cmd = "@PJL INFO STATUS\r\n", key = "Status" },
54
+ { cmd = "@PJL INFO PRODINFO\r\n", key = "ProductInfo" },
55
+ { cmd = "@PJL INFO MEMORY\r\n", key = "Memory" },
56
+ }
57
+
58
+ local function pjl_send(host, port, cmds)
59
+ local payload = UEL .. "@PJL\r\n"
60
+ for _, q in ipairs(cmds) do
61
+ payload = payload .. q.cmd
62
+ end
63
+ payload = payload .. UEL
64
+
65
+ local status, resp = comm.exchange(host, port, payload,
66
+ { timeout = 7000, bytes = 16384 })
67
+ if status then return resp end
68
+ return nil
69
+ end
70
+
71
+ local function extract_field(text, pattern)
72
+ local val = text:match(pattern)
73
+ if val then return val:match("^%s*(.-)%s*$") end
74
+ return nil
75
+ end
76
+
77
+ action = function(host, port)
78
+ local resp = pjl_send(host, port, PJL_QUERIES)
79
+ if not resp then
80
+ return "No PJL response — port may be closed or require credentials"
81
+ end
82
+
83
+ local out = stdnse.output_table()
84
+
85
+ -- Basic identity
86
+ local id_val = extract_field(resp, "@PJL INFO ID%s*\r?\n(.-)%r?\n%s*@PJL")
87
+ or extract_field(resp, 'ID%s*=%s*"([^"]+)"')
88
+ or extract_field(resp, "ID%s*\r?\n%s*([^\r\n]+)")
89
+ if id_val then out["ID"] = id_val end
90
+
91
+ -- Firmware
92
+ local fw = extract_field(resp, "DATECODE%s*=%s*([%w%.%-]+)")
93
+ or extract_field(resp, "[Ff]irmware[Vv]ersion%s*=%s*([%w%.%-]+)")
94
+ if fw then out["Firmware"] = fw end
95
+
96
+ -- Serial
97
+ local sn = extract_field(resp, "SERIALNUMBER%s*=%s*([%w%-]+)")
98
+ or extract_field(resp, "SERIAL%s*=%s*([%w%-]+)")
99
+ if sn then out["Serial"] = sn end
100
+
101
+ -- Resolution
102
+ local res = extract_field(resp, "RESOLUTION%s*=%s*(%d+)")
103
+ if res then out["Resolution-DPI"] = res end
104
+
105
+ -- Memory
106
+ local mem = extract_field(resp, "TOTALMEMORY%s*=%s*(%d+)")
107
+ if mem then out["Memory-KB"] = mem end
108
+
109
+ -- Filesystem
110
+ local fs = extract_field(resp, "VOLUME%s*=%s*([^\r\n]+)")
111
+ or extract_field(resp, "NAME%s*=%s*\"([^\"]+)\"")
112
+ if fs then out["Filesystem"] = fs end
113
+
114
+ -- Job count / status
115
+ local jobs = extract_field(resp, "JOBNAME%s*=%s*\"([^\"]+)\"")
116
+ if jobs then out["Last-Job"] = jobs end
117
+
118
+ -- PJL Password check: try @PJL DEFAULT PASSWORD test
119
+ local pwd_check = UEL .. "@PJL\r\n@PJL DEFAULT PASSWORD=0\r\n" .. UEL
120
+ local ps, pr = comm.exchange(host, port, pwd_check, { timeout = 3000, bytes = 512 })
121
+ local pwd_set = false
122
+ if ps and pr and (pr:match("PASSWORD") or pr:match("ACCESS")) then
123
+ pwd_set = true
124
+ out["PJL-Password"] = "SET (access control enabled)"
125
+ else
126
+ out["PJL-Password"] = "NOT SET — PJL unrestricted (job injection / NVRAM access possible)"
127
+ pwd_set = false
128
+ end
129
+
130
+ -- Verdict
131
+ local is_vuln = not pwd_set
132
+ local is_possible = resp and resp:match("READY") ~= nil
133
+
134
+ if is_vuln then
135
+ out["Verdict"] = "POSSIBLY VULNERABLE — unrestricted PJL: use printer-vuln-check for deep scan"
136
+ out["Suggest"] = "printerxpl-forge run --module xpl/edb-cve-2017-2741 --target " .. host.ip
137
+ elseif is_possible then
138
+ out["Verdict"] = "NOT VULNERABLE to unrestricted PJL (password set)"
139
+ else
140
+ out["Verdict"] = "UNKNOWN — partial PJL response"
141
+ end
142
+
143
+ out["Full-Exploitation"] = "pip install printerxpl-forge | printerxpl-forge run --target " .. host.ip
144
+
145
+ return out
146
+ end