import javax.swing.*; import java.util.*; import java.util.regex.*; import java.net.*; import java.math.*; import java.io.*; import snmp.*; import javax.mail.*; import javax.mail.internet.*; import javax.mail.*; import javax.mail.internet.*; class StingSelf { // class that finds and reports my own OS platform, and a list of the IP and // MAC addresses of each of my non-loopback network interfaces // // also handles sending the email notification public String platform; public String ipAddress[] = new String[24]; public String macAddress[] = new String[24]; public String emailAddress = "sting"; public boolean failed = false; public void listInfo() // prints to stdout the platform we're operating on, plus a table of // all IP addresses and their associated mac addresses { System.out.println("Platform: " + platform); System.out.println("IP Address/MAC address pairings:"); for (int index = 0; ipAddress[index] != null; index++) { System.out.println(ipAddress[index] + " " + macAddress[index]); } } public StingSelf() // discovers our OS platform, IP addresses, and associated mac addresses // as there isn't anything in the java library to handle gathering our // mac address information we're forced to fork a shell to run an OS command // constructor function can't have return value so it sets local boolean // "failed" to true if a problem occured gathering any of this info { String windowsCommand[] = {"ipconfig","/all"}; String posixCommand[] = {"/sbin/ifconfig","-a"}; try { // FIND THE OPERATING SYSTEM platform = System.getProperty("os.name"); if (!(platform.startsWith("Windows")) && !(platform.startsWith("Linux")) && !(platform.startsWith("SunOS"))) { throw new IOException("Unknown operating system: " + platform); } // FIND OUR IP ADDRESSES Pattern ipPattern = Pattern.compile("[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}"); // define a regexp that matches IP addresses NetworkInterface tempInterface; Enumeration m = NetworkInterface.getNetworkInterfaces(); // m now has a list of all of my local network interfaces for (int index = 0; m.hasMoreElements(); ) // for each interface in the list m { // assign the interface to a temp var and move on to the next one tempInterface = (NetworkInterface) m.nextElement(); Matcher ipMatch = ipPattern.matcher(tempInterface.toString()); // ipMatch.find() is boolean for whether a match was found, ipMatch.group() returns the actual match if (ipMatch.find() && !(ipMatch.group().equals("127.0.0.1"))) { ipAddress[index] = ipMatch.group(); index++; } } //FIND OUR MAC ADDRESSES Pattern macPattern = Pattern.compile("((:?[0-9a-f]{2}[-:]){5}[0-9a-f]{2}).*", Pattern.CASE_INSENSITIVE); String[] command; if (platform.startsWith("Windows")) { command = windowsCommand; } else { command = posixCommand; } //IMPORTANT NOTE TO SELF: linux and solaris ifconfig return entirely different output! This routine needs to be tested on both. Process findMacProcess = Runtime.getRuntime().exec(command); BufferedReader reader = new BufferedReader(new InputStreamReader(findMacProcess.getInputStream())); String line = null; for (int index = 0; (line = reader.readLine()) != null; ) { Matcher macMatch = macPattern.matcher(line); if (macMatch.find()) { macAddress[index] = macMatch.group().replaceAll("[-:]"," "); if (macAddress[index].length() > 18) { macAddress[index] = macAddress[index].substring(0,18); } index++; } } reader.close(); } catch(Exception e) { System.out.println("Exception gathering info on self: " + e); failed = true; } } public int notifyByEmail(String from, String to, String mailServer, String subject, String body) // sends an email with the from:, to:, subject:, and body as specified in the // argument list via the mailserver specified { try { Properties props = new Properties(); props.put("mail.smtp.host", mailServer); Session session = Session.getDefaultInstance(props, null); Message msg = new MimeMessage(session); InternetAddress addressFrom = new InternetAddress(from); msg.setFrom(addressFrom); InternetAddress addressTo = new InternetAddress(to); msg.addRecipient(Message.RecipientType.TO, addressTo); msg.setSubject(subject); msg.setContent(body, "text/plain"); Transport.send(msg); return 0; } catch (Exception e) { System.out.println("Exception sending email notification: " + e); return -1; } } } class StingRouter { // class that handles communication with the router public boolean failed = false; public String ipAddress; private String community; private int numInterfaces; public void listInfo() // prints, to stdout, the ip address of the default gateway, and the number // of interfaces it has { System.out.println("Default gateway is " + ipAddress); System.out.println("Default gateway has " + numInterfaces + " interfaces."); } public StingRouter(StingSelf self, String comString) // gathers the ip address and number of interfaces of the default gateway // since java doesn't give us a way to find out our default gateway we fork // a shell and use an os command to do that. the only way to find out the // number of interfaces on the router is to ask it in snmp, which is why we // need the community string at this point { String windowsCommand[] = {"route","PRINT"}; String posixCommand[] = {"netstat","-rn"}; try { community = comString; //FIND ITS IP ADDRESS String[] command; Pattern gatewayPattern; if (self.platform.startsWith("Windows")) { command = windowsCommand; gatewayPattern = Pattern.compile("0\\.0\\.0\\.0\\s*0\\.0\\.0\\.0\\s*(\\d{3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})"); } else { command = posixCommand; gatewayPattern = Pattern.compile("0\\.0\\.0\\.0\\s*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\s*0\\.0\\.0\\.0"); } Process findGatewayProcess = Runtime.getRuntime().exec(command); BufferedReader reader = new BufferedReader(new InputStreamReader(findGatewayProcess.getInputStream())); for (String line = null; (line = reader.readLine()) != null; ) { Matcher gatewayMatch = gatewayPattern.matcher(line); if (gatewayMatch.find()) { ipAddress = gatewayMatch.group(1); } } reader.close(); if (ipAddress == null) throw new IOException("Unable to determine default gateway."); //FIND ITS NUMBER OF INTERFACES int version = 0; InetAddress hostAddress = InetAddress.getByName(ipAddress); SNMPv1CommunicationInterface comInterface = new SNMPv1CommunicationInterface(version, hostAddress, community); String oid = "1.3.6.1.2.1.2.1.0"; SNMPVarBindList newVars = comInterface.getMIBEntry(oid); SNMPSequence pair = (SNMPSequence)(newVars.getSNMPObjectAt(0)); SNMPObject snmpValue = pair.getSNMPObjectAt(1); BigInteger notWhatIWanted = (BigInteger) snmpValue.getValue(); numInterfaces = notWhatIWanted.intValue(); } catch(Exception e) { System.out.println("Exception gathering info on router: " + e); failed = true; } } public boolean checkArpCache(StingSelf self, boolean failsafe) // checks the arp cache of the router to make sure it doesn't have a bogus // entry for us. if failsafe is set we want to report the check failed if // we get weird snmp exceptions BUT that isn't working yet { try { boolean result = true; int version = 0; InetAddress hostAddress = InetAddress.getByName(ipAddress); SNMPv1CommunicationInterface comInterface = new SNMPv1CommunicationInterface(version, hostAddress, community); for (int index = 0; (index < 24) && (self.ipAddress[index] != null); index++) { // the snmp mib divides the arp cache into seperate tables for each // interface, and since we don't know what number interface we are // connected to, we need to check the table for each interface for (int subIndex = 1; subIndex <= numInterfaces; subIndex++) { try { String oid = "1.3.6.1.2.1.4.22.1.2." + subIndex + "." + self.ipAddress[index]; SNMPVarBindList newVars = comInterface.getMIBEntry(oid); SNMPSequence pair = (SNMPSequence)(newVars.getSNMPObjectAt(0)); SNMPObject snmpValue = pair.getSNMPObjectAt(1); SNMPOctetString oString = (SNMPOctetString) snmpValue; String snmpString = oString.toHexString(); if (snmpString.equalsIgnoreCase(self.macAddress[index]) == false) { result = false; } } catch (SNMPGetException e) { // an exception of this type just means we asked the wrong table this time if (e.errorStatus != e.VALUE_NOT_AVAILABLE) { if (failsafe) return false; } } } } return result; } catch(Exception e) { System.out.println("Exception checking arp table: " + e); if (failsafe) return false; return true; } } } public class Sting { String emailTo, emailMsg, snmpCommunity; public static void main( String[] args ) { try { boolean failsafe = false; String snmpCommunity = "public"; String emailServer = null; String emailTo = null; String emailFrom = null; String emailMsg = "arp cache poisoning detected"; Pattern snmpConPattern = Pattern.compile("^-snmp_com:.*"); Pattern emailServerPattern = Pattern.compile("^-email_server:.*"); Pattern emailToPattern = Pattern.compile("^-email_to:.*"); Pattern emailFromPattern = Pattern.compile("^-email_from:.*"); Pattern emailMsgPattern = Pattern.compile("^-email_msg:.*"); for (int index = 0; index < args.length; index++) { if (args[index].equals("-help")) { printUsage(); return; } if (args[index].equals("-failsafe")) { failsafe = true; continue; } Matcher snmpConMatch = snmpConPattern.matcher(args[index]); Matcher emailServerMatch = emailServerPattern.matcher(args[index]); Matcher emailToMatch = emailToPattern.matcher(args[index]); Matcher emailFromMatch = emailFromPattern.matcher(args[index]); Matcher emailMsgMatch = emailMsgPattern.matcher(args[index]); if (snmpConMatch.find()) { snmpCommunity = snmpConMatch.group().substring(10); continue; } if (emailServerMatch.find()) { emailServer = emailServerMatch.group().substring(14); continue; } if (emailToMatch.find()) { emailTo = emailToMatch.group().substring(10); continue; } if (emailFromMatch.find()) { emailFrom = emailFromMatch.group().substring(12); continue; } if (emailMsgMatch.find()) { emailMsg = emailMsgMatch.group().substring(11); continue; } } if ((emailTo == null) || (emailServer == null)) { printUsage(); return; } StingSelf self = new StingSelf(); if (self.failed) { System.out.println("could not gather initial info on self"); return; } if (emailFrom == null) { emailFrom = "sting@" + self.ipAddress[0]; } StingRouter router = new StingRouter(self, snmpCommunity); if (router.failed) { System.out.println("could not gather initial info on router"); if (failsafe) self.notifyByEmail(emailFrom, emailTo, emailServer, "sting", emailMsg); return; } while(true) { if (router.checkArpCache(self, failsafe) == false) { System.out.println("arp cache poisoning detected."); self.notifyByEmail(emailFrom, emailTo, emailServer, "sting", emailMsg); Thread.sleep(1000); } } } catch(Exception e) { System.out.println("Exception during sting operation: " + e + "\n"); } } public static void printUsage() { System.out.println("sting accepts the following arguments: "); System.out.println(" -email_server:X where X is the hostname of the mailserver (mandatory)"); System.out.println(" -email_to:X where X is a valid email address (mandatory)"); System.out.println(" -email_msg:X where X is the body of the notification email to be sent"); System.out.println(" -snmp_com:X where X is the SNMP community string to use"); System.out.println(" -failsafe send an email if an SNMP query fails"); System.out.println(" -help print this usage statement and do nothing else"); } }