1# Copyright (C) 2017 Open Information Security Foundation 2# Copyright (c) 2013 Jason Ish 3# 4# You can copy, redistribute or modify this Program under the terms of 5# the GNU General Public License version 2 as published by the Free 6# Software Foundation. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# version 2 along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 16# 02110-1301, USA. 17 18"""Provide mappings from ID's to descriptions. 19 20Includes mapping classes for event ID messages and classification 21information. 22""" 23 24from __future__ import print_function 25 26import re 27 28class SignatureMap(object): 29 """SignatureMap maps signature IDs to a signature info dict. 30 31 The signature map can be build up from classification.config, 32 gen-msg.map, and new and old-style sid-msg.map files. 33 34 The dict's in the map will have at a minimum the following 35 fields: 36 37 * gid *(int)* 38 * sid *(int)* 39 * msg *(string)* 40 * refs *(list of strings)* 41 42 Signatures loaded from a new style sid-msg.map file will also have 43 *rev*, *classification* and *priority* fields. 44 45 Example:: 46 47 >>> from idstools import maps 48 >>> sigmap = maps.SignatureMap() 49 >>> sigmap.load_generator_map(open("tests/gen-msg.map")) 50 >>> sigmap.load_signature_map(open("tests/sid-msg-v2.map")) 51 >>> print(sigmap.get(1, 2495)) 52 {'classification': 'misc-attack', 'rev': 8, 'priority': 0, 'gid': 1, 53 'sid': 2495, 54 'msg': 'GPL NETBIOS SMB DCEPRC ORPCThis request flood attempt', 55 'ref': ['bugtraq,8811', 'cve,2003-0813', 'nessus,12206', 56 'url,www.microsoft.com/technet/security/bulletin/MS04-011.mspx']} 57 58 """ 59 60 def __init__(self): 61 self.map = {} 62 63 def size(self): 64 return len(self.map) 65 66 def get(self, generator_id, signature_id): 67 """Get signature info by generator_id and signature_id. 68 69 :param generator_id: The generator id of the signature to lookup. 70 :param signature_id: The signature id of the signature to lookup. 71 72 For convenience, if the generator_id is 3 and the signature is 73 not found, a second lookup will be done using a generator_id 74 of 1. 75 76 """ 77 78 key = (generator_id, signature_id) 79 sig = self.map.get(key) 80 if sig is None and generator_id == 3: 81 return self.get(1, signature_id) 82 return sig 83 84 def load_generator_map(self, fileobj): 85 """Load the generator message map (gen-msg.map) from a 86 file-like object. 87 88 """ 89 for line in fileobj: 90 line = line.strip() 91 if not line or line.startswith("#"): 92 continue 93 gid, sid, msg = [part.strip() for part in line.split("||")] 94 entry = { 95 "gid": int(gid), 96 "sid": int(sid), 97 "msg": msg, 98 "refs": [], 99 } 100 self.map[(entry["gid"], entry["sid"])] = entry 101 102 def load_signature_map(self, fileobj, defaultgid=1): 103 """Load signature message map (sid-msg.map) from a file-like 104 object. 105 106 """ 107 108 for line in fileobj: 109 line = line.strip() 110 if not line or line.startswith("#"): 111 continue 112 parts = [p.strip() for p in line.split("||")] 113 114 # If we have at least 6 parts, attempt to parse as a v2 115 # signature map file. 116 try: 117 entry = { 118 "gid": int(parts[0]), 119 "sid": int(parts[1]), 120 "rev": int(parts[2]), 121 "classification": parts[3], 122 "priority": int(parts[4]), 123 "msg": parts[5], 124 "ref": parts[6:], 125 } 126 except: 127 entry = { 128 "gid": defaultgid, 129 "sid": int(parts[0]), 130 "msg": parts[1], 131 "ref": parts[2:], 132 } 133 self.map[(entry["gid"], entry["sid"])] = entry 134 135class ClassificationMap(object): 136 """ClassificationMap maps classification IDs and names to a dict 137 object describing a classification. 138 139 :param fileobj: (Optional) A file like object to load 140 classifications from on initialization. 141 142 The classification dicts stored in the map have the following 143 fields: 144 145 * name *(string)* 146 * description *(string)* 147 * priority *(int)* 148 149 Example:: 150 151 >>> from idstools import maps 152 >>> classmap = maps.ClassificationMap() 153 >>> classmap.load_from_file(open("tests/classification.config")) 154 155 >>> classmap.get(3) 156 {'priority': 2, 'name': 'bad-unknown', 'description': 'Potentially Bad Traffic'} 157 >>> classmap.get_by_name("bad-unknown") 158 {'priority': 2, 'name': 'bad-unknown', 'description': 'Potentially Bad Traffic'} 159 160 """ 161 162 def __init__(self, fileobj=None): 163 self.id_map = [] 164 self.name_map = {} 165 166 if fileobj: 167 self.load_from_file(fileobj) 168 169 def size(self): 170 return len(self.id_map) 171 172 def add(self, classification): 173 """Add a classification to the map.""" 174 self.id_map.append(classification) 175 self.name_map[classification["name"]] = classification 176 177 def get(self, class_id): 178 """Get a classification by ID. 179 180 :param class_id: The classification ID to get. 181 182 :returns: A dict describing the classification or None. 183 184 """ 185 if 0 < class_id <= len(self.id_map): 186 return self.id_map[class_id - 1] 187 else: 188 return None 189 190 def get_by_name(self, name): 191 """Get a classification by name. 192 193 :param name: The name of the classification 194 195 :returns: A dict describing the classification or None. 196 197 """ 198 if name in self.name_map: 199 return self.name_map[name] 200 else: 201 return None 202 203 def load_from_file(self, fileobj): 204 """Load classifications from a Snort style 205 classification.config file object. 206 207 """ 208 pattern = "config classification: ([^,]+),([^,]+),([^,]+)" 209 for line in fileobj: 210 m = re.match(pattern, line.strip()) 211 if m: 212 self.add({ 213 "name": m.group(1), 214 "description": m.group(2), 215 "priority": int(m.group(3))}) 216