From dcedb1ad2ece2986ca1b1e811e53e3bfc4238f5d Mon Sep 17 00:00:00 2001 From: Michal Kostenec <kostenec@civ.zcu.cz> Date: Fri, 28 Nov 2014 09:47:13 +0100 Subject: [PATCH] SQL features Authorization Authentication --- .gitignore | 1 + warden3/warden_server/warden_server.cfg | 2 +- warden3/warden_server/warden_server.py | 225 ++++++++++++++++++++++-- 3 files changed, 208 insertions(+), 20 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfaad76 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pem diff --git a/warden3/warden_server/warden_server.cfg b/warden3/warden_server/warden_server.cfg index 6a40f8b..65f7ce7 100644 --- a/warden3/warden_server/warden_server.cfg +++ b/warden3/warden_server/warden_server.cfg @@ -8,4 +8,4 @@ "get_events_limit": 1000, "description": "Warden 3 not even alpha development server" } -} +} \ No newline at end of file diff --git a/warden3/warden_server/warden_server.py b/warden3/warden_server/warden_server.py index 80ce007..b80951c 100755 --- a/warden3/warden_server/warden_server.py +++ b/warden3/warden_server/warden_server.py @@ -155,7 +155,7 @@ class NoAuthenticator(Object): return "anybody" # or None - def authorize(self, env, client, method, args): + def authorize(self, env, client, method, event, args): return (client is not None) @@ -192,10 +192,29 @@ class X509Authenticator(NoAuthenticator): return self.db.get_client_by_name(names) - def authorize(self, env, client, method, args): - return (client is not None) and client["rights"]=="whatever" - - + def authorize(self, env, client, method, event, args): + cl = None + service = event['Node'][0]['Name'] + + for i in range(len(client)): + if client[i]['service'] == service: + cl = client[i] + break + + if cl is None: + return None + + # logging.debug(cl) + # logging.debug(method) + # logging.debug(service) + + # return True if (method == 'getEvents' and cl['read']) + if ((method == 'sendEvents' and cl['write'] == 1) or + (method == 'getDebug' and cl['debug'] == 1) or + (method == 'sendEvents' and cl['test'] == 1 and service == 'Test')): + return cl + + return None class NoValidator(Object): @@ -203,7 +222,6 @@ class NoValidator(Object): return [] - class JSONSchemaValidator(NoValidator): def __init__(self, filename=None): @@ -256,14 +274,15 @@ class MySQL(Object): def get_client_by_name(self, name): - return { - "name": name[0] if name else None, - "rights": "whatever" - } + format_strings = ','.join(['%s'] * len(name)) + self.crs.execute("SELECT `id`, `hostname`, `service`, `identity`, `read`, `write`, `debug`, `test` FROM `clients2` WHERE `valid` = 1 AND `hostname` IN (%s)" % format_strings, tuple(name)) + row = self.crs.fetchall() + + return row if row else None def get_debug(self): - self.crs.execute("SELECT VERSION() AS VER"); + self.crs.execute("SELECT VERSION() AS VER") row = self.crs.fetchone() return { "db": "MySQL", @@ -296,16 +315,176 @@ class MySQL(Object): cat=None, nocat=None, tag=None, notag=None, group=None, nogroup=None): + + sqlwhere = [] + sqltemp = {} + + + if cat is not None and nocat is not None: + raise Error("Unrealizable conditions. Choose cat or nocat option.", 500, method='getEvents', + exc=sys.exc_info(), detail={'cat': cat, 'nocat' : nocat}) + + if cat is not None or nocat is not None: + if cat is not None: + sqltemp['cat'] = generateDynamicQuery(self, "Category", "category_id IN (%s)", json.loads(cat)) + if nocat is not None: + sqltemp['cat'] = generateDynamicQuery(self, "Category", "category_id NOT IN (%s)", json.loads(nocat)) + + sqlwhere.append("e.id IN (SELECT event_id FROM event_category_mapping4 WHERE %s)" % sqltemp['cat']) + + if tag is not None and notag is not None: + raise Error("Unrealizable conditions. Choose tag or notag option.", 500, method='getEvents', + exc=sys.exc_info(), detail={'tag': cat, 'notag' : nocat}) + + if tag is not None or notag is not None: + if tag is not None: + sqltemp['tag'] = generateDynamicQuery(self, "Tag", "tag_id IN (%s)", json.loads(tag)) + if notag is not None: + sqltemp['tag'] = generateDynamicQuery(self, "Tag", "tag_id NOT IN (%s)", json.loads(notag)) + + sqlwhere.append("e.id IN (SELECT event_id FROM event_tag_mapping4 WHERE %s)" % sqltemp['tag']) + + + if group is not None and nogroup is not None: + raise Error("Unrealizable conditions. Choose group or nogroup option.", 500, method='getEvents', + exc=sys.exc_info(), detail={'tag': cat, 'notag' : nocat}) + + if group is not None or nogroup is not None: + sqltemp['group'] = "" + + if group is not None: + for identity in json.loads(group): + sqltemp['group'] += ("cl.identity LIKE '%s' AND " % (identity)) + if nogroup is not None: + for identity in json.loads(nogroup): + sqltemp['group'] += ("cl.identity NOT LIKE '%s' AND " % (identity)) + + # logging.debug(sqltemp['group'][:-4]) + sqlwhere.append(sqltemp['group'][:-4]) + + sqlwhere_string = (" AND " . join(sqlwhere)) + # logging.debug(sqlwhere_string) + # logging.debug(' AND ' . join(sqlwhere)) + + #sqlwhere = sqlwhere[:-4] + and_op = "" if not sqlwhere_string else "AND" + + # logging.debug("SELECT e.id, e.data FROM clients2 cl RIGHT JOIN events4 e ON cl.id = e.client_id WHERE e.id > %s AND %s %s e.valid = 1 LIMIT %s" % (str(id), sqlwhere_string, and_op, str(count))) + self.crs.execute("SELECT e.id, e.data FROM clients2 cl RIGHT JOIN events4 e ON cl.id = e.client_id WHERE e.id > %s AND %s %s e.valid = 1 LIMIT %s" % (str(id), sqlwhere_string, and_op, str(count))) + row = self.crs.fetchall() + return { - "lastid": (id or 0)+count, - "events": [self.gen_random_idea() for i in range(count)] + "lastid": row[-1]['id'] if row else str(id), + "events": [row[i]['data'] for i in range(len(row))] } def store_events(self, client, events): errs = [] # See sendEvents and validation, should return something similar + + for event in events: + try: + # logging.debug("INSERT INTO events5 (detected,received,client_id,data) VALUES ('%s', NOW(), '%s', '%s')" % (event['DetectTime'], client['id'], self.con.escape_string(str(event)))) + self.crs.execute("INSERT INTO events5 (detected,received,client_id,data) VALUES ('%s', NOW(), '%s', '%s')" % (event['DetectTime'], client['id'], self.con.escape_string(str(event)))) + lastid = self.crs.lastrowid + # logging.debug(str(lastid)) + for cat in event['Category']: + # logging.debug({'cat': cat}) + cat_id = self.map_id('Category', cat) if self.map_id('Category', cat) else self.map_id('Category', 'Other.Other') + # logging.debug({'cat_id': cat_id}) + # logging.debug("INSERT INTO event_category_mapping5 (event_id,category_id) VALUES ('%s', '%s')" % (str(lastid), str(cat_id))) + self.crs.execute("INSERT INTO event_category_mapping5 (event_id,category_id) VALUES ('%s', '%s')" % (str(lastid), str(cat_id))) + + for tag in event['Node'][0]['Tags']: + tag_id = self.map_id('Tag', tag) if self.map_id('Tag', tag) else self.map_id('Tag', 'Other') + # logging.debug({'tag_id': tag_id}) + # logging.debug("INSERT INTO event_tag_mapping5 (event_id,tag_id) VALUES ('%s', '%s')" % (str(lastid), tag_id)) + self.crs.execute("INSERT INTO event_tag_mapping5 (event_id,tag_id) VALUES ('%s', '%s')" % (str(lastid), str(tag_id))) + + self.con.commit() + except: + self.con.rollback() + raise Error("Data storing error", 00, detail={'event': event}) + errs.append({"event": event}) + return errs + def map_id (self, section, key): + # Should by placed in config file + data = {} + data['Tag'] = { + "Connection" : 1, + "Datagram" : 2, + "Content" : 3, + "Data" : 4, + "File" : 5, + "Flow" : 6, + "Log": 7, + "Protocol" : 8, + "Host" : 9, + "Network" : 10, + "Correlation" : 11, + "External" : 12, + "Reporting" : 13, + "Other" : 99 + } + + data['Category'] = { + "Abusive.Spam" : 101, + "Abusive.Harassment" : 102, + "Abusive.Child" : 103, + "Abusive.Sexual" : 104, + "Abusive.Violence" : 105, + "Malware.Virus" : 201, + "Malware.Worm" : 202, + "Malware.Trojan" : 203, + "Malware.Spyware" : 204, + "Malware.Dialer" : 205, + "Malware.Rootkit" : 206, + "Recon.Scanning" : 301, + "Recon.Sniffing" : 302, + "Recon.SocialEngineering" : 303, + "Recon.Searching" : 304, + "Attempt.Exploit" : 401, + "Attempt.Login" : 402, + "Attempt.NewSignature" : 403, + "Intrusion.AdminCompromise" : 501, + "Intrusion.UserCompromise" : 502, + "Intrusion.AppCompromise" : 503, + "Intrusion.Botnet" : 504, + "Availability.DoS" : 601, + "Availability.DDoS" : 602, + "Availability.Sabotage" : 603, + "Availability.Outage" : 604, + "Information.UnauthorizedAccess" : 701, + "Information.UnauthorizedModification" : 702, + "Fraud.UnauthorizedUsage" : 801, + "Fraud.Copyright" : 802, + "Fraud.Masquerade" : 803, + "Fraud.Phishing" : 804, + "Fraud.Scam" : 805, + "Vulnerable.Open" : 901, + "Anomaly.Traffic" : 1001, + "Anomaly.Connection" : 1002, + "Anomaly.Protocol" : 1003, + "Anomaly.System" : 1004, + "Anomaly.Application" : 1005, + "Anomaly.Behaviour" : 1006, + "Other" : 9998, + "Test" : 9999, + } + + try: + return data[section][key] + except: + return data[section]['Other'] + +def generateDynamicQuery(self, section, query_string, variables): + variables_id = [self.map_id(section, v) for v in variables] + format_strings = ','.join(['\'%s\''] * len(variables_id)) + temp_string = query_string % format_strings + + return temp_string % tuple(variables_id) def expose(meth): @@ -365,7 +544,7 @@ class Server(Object): client = self.auth.authenticate(environ) if not client: - raise Error("I'm watching YOU.", 403, method=path) + raise Error("I'm watching YOU. (Authenticate)", 403, method=path) try: events = json.loads(injson) if injson else None @@ -380,8 +559,8 @@ class Server(Object): if events: args["events"] = events - if not self.auth.authorize(environ, client, path, args): - raise Error("I'm watching YOU.", 403, method=path, detail={"client": client}) + # if not self.auth.authorize(environ, client, path, args): + # raise Error("I'm watching YOU.", 403, method=path, detail={"client": client}) args = self.sanitize_args(path, method, args) result = method(_env=environ, _client=client, **args) # call requested method @@ -428,10 +607,11 @@ class Server(Object): class WardenHandler(Object): - def __init__(self, validator, db, + def __init__(self, validator, db, auth, send_events_limit=100000, get_events_limit=100000, description=None): + self.auth = auth self.db = db self.validator = validator self.send_events_limit = send_events_limit @@ -507,13 +687,17 @@ class WardenHandler(Object): okevents = [] valerrs = [] for event in events: + auth_cl = self.auth.authorize(_env, _client, 'sendEvents', event, None) + if not auth_cl: + raise Error("I'm watching YOU. (Authorization)", 403, method='sendEvents', detail={"client": _client}) + verrs = self.validator.check(event) if verrs: valerrs.append({"errors": verrs, "event": event}) else: okevents.append(event) - dberrs = self.db.store_events(_client, okevents) + dberrs = self.db.store_events(auth_cl, okevents) if valerrs or dberrs: raise Error("Event storage error", 500, method="sendEvents", @@ -633,12 +817,13 @@ def build_server(conf): "host": {"type": str, "default": "localhost"}, "user": {"type": str, "default": "warden"}, "password": {"type": str, "default": ""}, - "dbname": {"type": str, "default": "warden3"}, + "dbname": {"type": str, "default": "warden3c"}, "port": {"type": natural, "default": 3306} }, "WardenHandler": { "validator": {"type": obj, "default": "validator"}, "db": {"type": obj, "default": "DB"}, + "auth": {"type": obj, "default": "auth"}, "send_events_limit": {"type": natural, "default": 10000}, "get_events_limit": {"type": natural, "default": 10000}, "description": {"type": str, "default": ""} @@ -654,6 +839,8 @@ def build_server(conf): sect_name = sect_name.lower() sect_def = section_def[sect_name] + #logging.debug("Testing %s" % sect_name) + try: # Object type defined? objtype = config["type"] del config["type"] -- GitLab