This commit is contained in:
Phrozyn 2017-10-10 10:55:13 -05:00
Родитель 6081bee46b 44ee604253
Коммит b6d5d1b57c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: DBCDDDC9CF758282
35 изменённых файлов: 2723 добавлений и 107 удалений

Просмотреть файл

Просмотреть файл

@ -29,8 +29,9 @@ class broNSM(AlertTask):
search_query.add_must([
PhraseMatch("details.note", "MozillaAlive::Bro_Is_Watching_You"),
PhraseMatch("details.peer_descr", host),
TermMatch('category', 'bronotice'),
TermMatch('_type', 'bro')
TermMatch('category', 'bro'),
TermMatch('type', 'notice'),
TermMatch('_type', 'nsm')
])
self.filtersManual(search_query)

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

2
alerts/ssh_key.conf Normal file
Просмотреть файл

@ -0,0 +1,2 @@
# host_regex path
.*\.ignorehosts\.com /home/user/.ssh/id_rsa

101
alerts/ssh_key.py Normal file
Просмотреть файл

@ -0,0 +1,101 @@
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Copyright (c) 2017 Mozilla Corporation
#
# Contributors:
# Aaron Meihm <ameihm@mozilla.com>
from lib.alerttask import AlertTask
from query_models import SearchQuery, TermMatch
import re
# This alert consumes data produced by the MIG sshkey module and mig-runner.
# ssh key related events are compared against a whitelist which is the
# alerts configuration file (sshkey.conf). The format of this whitelist
# is as follows:
#
# <host regex> <path>
#
# If a host matches the regex, and the detected key matches the path, the
# alert will not generate an alert event. If the detected key is not in
# the whitelist, an alert will be created.
class SSHKey(AlertTask):
def __init__(self):
# _whitelist contains all whitelisted key paths, loaded from the
# configuration file for the alert plugin
self._whitelist = []
AlertTask.__init__(self)
self._parse_whitelist('ssh_key.conf')
def main(self):
search_query = SearchQuery(minutes=30)
search_query.add_must([
TermMatch('_type', 'event'),
TermMatch('tags', 'mig-runner-sshkey'),
])
self.filtersManual(search_query)
self.searchEventsSimple()
self.walkEvents()
# Load whitelist from file system and store in object, path specifies the
# path to load the whitelist from
def _parse_whitelist(self, path):
with open(path) as fd:
lns = [x.strip() for x in fd.readlines()]
for entry in lns:
try:
pindex = entry.index(' ')
except ValueError:
continue
self._whitelist.append({
'hostre': entry[:pindex],
'path': entry[pindex + 1:]
})
# Return false if the key path is present in the whitelist, otherwise return
# true
def _whitelist_check(self, hostname, privkey):
for went in self._whitelist:
try:
rem = re.compile(went['hostre'])
except:
continue
if rem.match(hostname) == None:
continue
if privkey['path'] == went['path']:
return False
return True
def onEvent(self, event):
# Each event could potentially contain a number of identified private keys, since
# mig-runner will send an event per host. Compare the private keys in the event
# to the keys in the whitelist, if any are missing from the whitelist we will include
# the paths in an alert and return the alert, otherwise no alert is returned.
ev = event['_source']
if 'details' not in ev:
return None
if 'private' not in ev['details'] or 'agent' not in ev['details']:
return None
hostname = ev['details']['agent']
alertkeys = [x for x in ev['details']['private'] if self._whitelist_check(hostname, x)]
if len(alertkeys) == 0:
return None
category = 'sshkey'
tags = ['sshkey']
severity = 'WARNING'
summary = 'Private keys detected on {} missing from whitelist'.format(hostname)
ret = self.createAlertDict(summary, category, tags, [event], severity)
ret['details'] = {
'private': alertkeys
}
return ret

Просмотреть файл

@ -0,0 +1,40 @@
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Copyright (c) 2017 Mozilla Corporation
from lib.alerttask import AlertTask
from query_models import SearchQuery, TermMatch, PhraseMatch
class AlertSSHPasswordAuthViolation(AlertTask):
def main(self):
search_query = SearchQuery(hours=4)
search_query.add_must([
TermMatch('_type', 'event'),
PhraseMatch('tags', 'ssh_password_auth_policy_violation'),
])
self.filtersManual(search_query)
# Search aggregations on field 'sourceipaddress', keep X samples of
# events at most
self.searchEventsAggregated('details.destinationipaddress', samplesLimit=100)
# alert when >= X matching events in an aggregation
self.walkAggregations(threshold=1)
# Set alert properties
def onAggregation(self, aggreg):
# aggreg['count']: number of items in the aggregation, ex: number of failed login attempts
# aggreg['value']: value of the aggregation field, ex: toto@example.com
# aggreg['events']: list of events in the aggregation
category = 'ssh_password_auth_policy_violation'
tags = ['ssh_password_auth_policy_violation']
severity = 'WARNING'
summary = 'SSH password authentication allowed on {0}'.format(aggreg['value'])
# Create the alert object based on these properties
return self.createAlertDict(summary, category, tags, aggreg['events'], severity)

Просмотреть файл

@ -1,23 +1,8 @@
if $programname == 'mozdefbot-worker' then /var/log/mozdef/mozdefbot.log
if $programname == 'loginput-worker' then /var/log/mozdef/loginput.log
if $programname == 'infosecsqs-worker' then /var/log/mozdef/infosecsqs.log
if $programname == 'restapi-worker' then /var/log/mozdef/restapi.log
if $programname == 'eventtask-worker' then /var/log/mozdef/eventtask.log
if $programname == 'nubis-worker' then /var/log/mozdef/nubis.log
if $programname == 'bro-worker' then /var/log/mozdef/bro.log
if $programname == 'migsqs-worker' then /var/log/mozdef/migsqs.log
if $programname == 'parsyssqs-worker' then /var/log/mozdef/parsyssqs.log
if $programname == 'autoland-worker' then /var/log/mozdef/autoland.log
if $programname == 'contegix-worker' then /var/log/mozdef/contegix.log
if $programname == 'deis-worker' then /var/log/mozdef/deis.log
if $programname == 'releng-worker' then /var/log/mozdef/releng.log
if $programname == 'fxa-worker' then /var/log/mozdef/fxa.log
if $programname == 'httpobs-worker' then /var/log/mozdef/httpobs.log
if $programname == 'riskheatmap-worker' then /var/log/mozdef/riskheatmap.log
if $programname == 'ssosqs-worker' then /var/log/mozdef/sso.log
if $programname == 'cloudtrail-worker' then /var/log/mozdef/cloudtrail.log
if $programname == 'alertplugins-worker' then /var/log/mozdef/alertplugins.log
if $programname == 'contegix-auditd-worker' then /var/log/mozdef/contegix-auditd.log
if $programname == 'mongod.3002' then /var/log/mozdef/mongo/meteor-mongo.log
if $programname == 'mongod' then /var/log/mozdef/mongo/mongo.log
if $programname == 'kibana4' then /var/log/mozdef/kibana.log

Просмотреть файл

@ -125,6 +125,11 @@
"type" : "string",
"index" : "not_analyzed",
"doc_values" : true
},
"version" : {
"index" : "not_analyzed",
"type" : "string",
"doc_values" : true
}
},
"_all" : {

Просмотреть файл

@ -1,6 +1,6 @@
/var/log/mozdef/mongo/*.log
{
rotate 17
rotate 4
weekly
missingok
notifempty

Просмотреть файл

@ -1,6 +1,6 @@
/var/log/mozdef/*.log
{
rotate 17
rotate 4
weekly
missingok
notifempty

Просмотреть файл

@ -1,7 +1,7 @@
/var/log/mozdef/nginx/*.log {
/var/log/mozdef/nginx/*.error_log {
weekly
missingok
rotate 17
rotate 4
compress
delaycompress
notifempty

Просмотреть файл

@ -2,7 +2,7 @@
{
copytruncate
dateext
rotate 17
rotate 4
weekly
missingok
notifempty

Просмотреть файл

@ -9,7 +9,7 @@
# Aaron Meihm ameihm@mozilla.com
# Reads from papertrail using the API and inserts log data into ES in
# the same manner as esworker.py
# the same manner as esworker_eventtask.py
import json

Просмотреть файл

@ -115,7 +115,7 @@ class taskConsumer(object):
message_json = json.loads(message_value)
for inside_message_key, inside_message_value in message_json.iteritems():
if inside_message_key in ('processid', 'pid'):
processid = inside_message_value
processid = str(inside_message_value)
processid = processid.replace('[', '')
processid = processid.replace(']', '')
event['processid'] = processid

Просмотреть файл

@ -6,8 +6,12 @@
# Contributors:
# Jeff Bryner jbryner@mozilla.com
# Brandon Myers bmyers@mozilla.com
# Michal Purzynski mpurzynski@mozilla.com
import netaddr
from utilities.toUTC import toUTC
from datetime import datetime
from platform import node
def isIPv4(ip):
@ -41,39 +45,519 @@ class message(object):
self.registration = ['bro', 'nsm']
self.priority = 5
try:
self.mozdefhostname = u'{0}'.format(node())
except:
self.mozdefhostname = 'failed to fetch mozdefhostname'
pass
def onMessage(self, message, metadata):
# make sure I really wanted to see this message
# bail out early if not
if u'customendpoint' not in message:
return message, metadata
if u'category' not in message:
return message, metadata
if u'type' not in message:
return message, metadata
if message['category'] != 'bro':
return message, metadata
# set the doc type to bro
# to avoid data type conflicts with other doc types
# (int v string, etc)
metadata['doc_type']= 'bro'
# index holds documents of type 'type'
# index -> type -> doc
metadata['doc_type']= 'nsm'
# move Bro specific fields under 'details' while preserving metadata
newmessage = dict()
newmessage['details'] = message
newmessage['customendpoint'] = 'bro'
# move some fields that are expected at the event 'root' where they belong
if 'hostname' in newmessage['details']:
newmessage['hostname'] = newmessage['details']['hostname']
del(newmessage['details']['hostname'])
if 'tags' in newmessage['details']:
newmessage['tags'] = newmessage['details']['tags']
del(newmessage['details']['tags'])
if 'category' in newmessage['details']:
newmessage['category'] = newmessage['details']['category']
del(newmessage['details']['category'])
if 'customendpoint' in newmessage['details']:
del(newmessage['details']['customendpoint'])
if 'type' in newmessage['details']:
newmessage['type'] = newmessage['details']['type']
del(newmessage['details']['type'])
# add mandatory fields
if 'ts' in newmessage['details']:
newmessage[u'utctimestamp'] = toUTC(newmessage['details']['ts']).isoformat()
newmessage[u'timestamp'] = toUTC(newmessage['details']['ts']).isoformat()
else:
# a malformed message somehow managed to crawl to us, let's put it somewhat together
newmessage[u'utctimestamp'] = toUTC(datetime.now()).isoformat()
newmessage[u'timestamp'] = toUTC(datetime.now()).isoformat()
newmessage[u'receivedtimestamp'] = toUTC(datetime.now()).isoformat()
newmessage[u'eventsource'] = u'nsm'
newmessage[u'severity'] = u'INFO'
newmessage[u'mozdefhostname'] = self.mozdefhostname
# re-arrange the position of some fields
# {} vs {'details':{}}
if 'details' in message.keys():
# details.tags -> tags
if 'tags' in message['details'].keys():
message['tags'] = message['details']['tags']
del message['details']['tags']
if 'details' in newmessage:
# details.summary -> summary
if 'summary' in message['details'].keys():
message['summary'] = message['details']['summary']
del message['details']['summary']
# All Bro logs need special treatment, so we provide it
# Not a known log type? Mark it as such and return
if 'type' not in newmessage:
newmessage['type'] = u'unknown'
return newmessage, metadata
else:
logtype = newmessage['type']
# clean up the action notice IP addresses
if 'actions' in message['details'].keys():
if message['details']['actions'] == "Notice::ACTION_LOG":
# retrieve indicator ip addresses from the sub field
# "sub": "Indicator: 1.2.3.4, Indicator: 5.6.7.8"
message['details']['indicators'] = [ip for ip
in findIPv4(message['details']['sub'])]
if logtype == 'conn':
newmessage[u'details'][u'originipbytes'] = newmessage['details']['orig_ip_bytes']
newmessage[u'details'][u'responseipbytes'] = newmessage['details']['resp_ip_bytes']
del(newmessage['details']['orig_ip_bytes'])
del(newmessage['details']['resp_ip_bytes'])
if 'history' not in newmessage['details']:
newmessage['details'][u'history'] = ''
newmessage[u'summary'] = (
u'{sourceipaddress}:'+
u'{sourceport} -> '+
u'{destinationipaddress}:'
u'{destinationport} '+
u'{history} '+
u'{originipbytes} bytes / '
u'{responseipbytes} bytes'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'files':
if 'rx_hosts' in newmessage['details']:
newmessage['details'][u'sourceipaddress'] = u'{0}'.format(newmessage['details']['rx_hosts'][0])
if 'tx_hosts' in newmessage['details']:
newmessage['details'][u'destinationipaddress'] = u'{0}'.format(newmessage['details']['tx_hosts'][0])
if 'mime_type' not in newmessage['details']:
newmessage['details'][u'mime_type'] = u'unknown'
if 'filename' not in newmessage['details']:
newmessage['details'][u'filename'] = u'unknown'
if 'total_bytes' not in newmessage['details']:
newmessage['details'][u'total_bytes'] = u'0'
if 'md5' not in newmessage['details']:
newmessage['details'][u'md5'] = u'None'
if 'source' not in newmessage['details']:
newmessage['details'][u'source'] = u'None'
newmessage[u'summary'] = (
u'{rx_hosts[0]} '
u'downloaded (MD5) '
u'{md5} '
u'filename {filename} '
u'MIME {mime_type} '
u'({total_bytes} bytes) '
u'from {tx_hosts[0]} '
u'via {source}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'dns':
if 'qtype_name' not in newmessage['details']:
newmessage['details'][u'qtype_name'] = u''
if 'query' not in newmessage['details']:
newmessage['details'][u'query'] = u''
if 'rcode_name' not in newmessage['details']:
newmessage['details'][u'rcode_name'] = u''
newmessage[u'summary'] = (
u'{sourceipaddress} -> '
u'{destinationipaddress}:{destinationport} '
u'{qtype_name} '
u'{query} '
u'{rcode_name}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'http':
if 'method' not in newmessage['details']:
newmessage['details'][u'method'] = u''
if 'host' not in newmessage['details']:
newmessage['details'][u'host'] = u''
if 'uri' not in newmessage['details']:
newmessage['details'][u'uri'] = u''
if 'status_code' not in newmessage['details']:
newmessage['details'][u'status_code'] = u''
newmessage[u'summary'] = (
u'{method} '
u'{host} '
u'{uri} '
u'{status_code}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'ssl':
if 'server_name' not in newmessage['details']:
# fake it till you make it
newmessage['details'][u'server_name'] = newmessage['details']['destinationipaddress']
newmessage[u'summary'] = (
u'SSL: {sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'{server_name}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'dhcp':
newmessage[u'summary'] = (
'{assigned_ip} assigned to '
'{mac}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'ftp':
if 'command' not in newmessage['details']:
newmessage['details'][u'command'] = u''
if 'user' not in newmessage['details']:
newmessage['details'][u'user'] = u''
newmessage[u'summary'] = (
u'FTP: {sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'{command} '
u'{user}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'pe':
if 'os' not in newmessage['details']:
newmessage['details']['os'] = ''
if 'subsystem' not in newmessage['details']:
newmessage['details']['subsystem'] = ''
newmessage[u'summary'] = (
u'PE file: {os} '
u'{subsystem}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'smtp':
if 'from' in newmessage['details']:
from_decoded = newmessage['details'][u'from'].decode('unicode-escape')
newmessage['details'][u'from'] = from_decoded
else:
newmessage['details'][u'from'] = u''
if 'to' not in newmessage['details']:
newmessage['details'][u'to'] = [u'']
if 'msg_id' not in newmessage['details']:
newmessage['details'][u'msg_id'] = u''
newmessage[u'summary'] = (
u'SMTP: {sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'from {from} '
u'to '
u'{to[0]} '
u'ID {msg_id}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'ssh':
if 'auth_success' not in newmessage['details']:
newmessage['details'][u'auth_success'] = u'unknown'
newmessage[u'summary'] = (
u'SSH: {sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'success {auth_success}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'tunnel':
if 'tunnel_type' not in newmessage['details']:
newmessage['details'][u'tunnel_type'] = u''
if 'action' not in newmessage['details']:
newmessage['details'][u'action'] = u''
newmessage[u'summary'] = (
u'{sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'{tunnel_type} '
u'{action}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'intel':
if 'seenindicator' not in newmessage['details']:
newmessage['details'][u'seenindicator'] = u''
newmessage[u'summary'] = (
u'Bro intel match: '
u'{seenindicator}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'knowncerts':
if 'serial' not in newmessage['details']:
newmessage['details'][u'serial'] = u'0'
newmessage[u'summary'] = (
u'Certificate seen from: '
u'{host}:'
u'{port_num} '
u'serial {serial}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'knowndevices':
if 'mac' not in newmessage['details']:
newmessage['details'][u'mac'] = u''
if 'dhcp_host_name' not in newmessage['details']:
newmessage['details'][u'dhcp_host_name'] = u''
newmessage[u'summary'] = (
u'New host: '
u'{mac} '
u'{dhcp_host_name}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'knownhosts':
if 'host' not in newmessage['details']:
newmessage['details'][u'host'] = u''
newmessage[u'summary'] = (
u'New host: '
u'{host}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'knownservices':
if 'service' not in newmessage['details']:
newmessage['details']['service'] = []
if not newmessage['details']['service']:
newmessage['details'][u'service'] = [u'Unknown']
if 'host' not in newmessage['details']:
newmessage['details'][u'host'] = u'unknown'
if 'port_num' not in newmessage['details']:
newmessage['details'][u'port_num'] = u'0'
if 'port_proto' not in newmessage['details']:
newmessage['details'][u'port_proto'] = u''
newmessage[u'summary'] = (
u'New service: '
u'{service[0]} '
u'on host '
u'{host}:'
u'{port_num} / '
u'{port_proto}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'notice':
newmessage['details'][u'indicators'] = []
if 'sub' not in newmessage['details']:
newmessage['details'][u'sub'] = u''
if 'msg' not in newmessage['details']:
newmessage['details'][u'msg'] = u''
if 'note' not in newmessage['details']:
newmessage['details'][u'note'] = u''
newmessage[u'summary'] = (
u'{note} '
u'{msg} '
u'{sub}'
).format(**newmessage['details'])
# clean up the action notice IP addresses
if 'actions' in newmessage['details']:
if newmessage['details']['actions'] == "Notice::ACTION_LOG":
# retrieve indicator ip addresses from the sub field
# "sub": "Indicator: 1.2.3.4, Indicator: 5.6.7.8"
newmessage['details']['indicators'] = [ip for ip
in findIPv4(newmessage['details']['sub'])]
# remove the details.src field and add it to indicators
# as it may not be the actual source.
if 'src' in message['details'].keys():
if isIPv4(message['details']['src']):
message['details']['indicators'].append(message['details']['src'])
del message['details']['src']
if 'src' in newmessage['details']:
if isIPv4(newmessage['details']['src']):
newmessage['details']['indicators'].append(newmessage['details']['src'])
del newmessage['details']['src']
return (newmessage, metadata)
if logtype == 'rdp':
if 'cookie' not in newmessage['details']:
newmessage['details'][u'cookie'] = u'unknown'
newmessage[u'summary'] = (
u'RDP: {sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'cookie {cookie}'
).format(**newmessage['details'])
return (newmessage, metadata)
return (message, metadata)
if logtype == 'sip':
if 'status_msg' not in newmessage['details']:
newmessage['details'][u'status_msg'] = u'unknown'
if 'uri' not in newmessage['details']:
newmessage['details'][u'uri'] = u'unknown'
if 'method' not in newmessage['details']:
newmessage['details'][u'method'] = u'unknown'
newmessage[u'summary'] = (
u'SIP: {sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'method {method} '
u'uri {uri} '
u'status {status_msg}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'software':
if 'name' not in newmessage['details']:
newmessage['details'][u'name'] = u'unparsed'
if 'software_type' not in newmessage['details']:
newmessage['details'][u'software_type'] = u'unknown software'
if 'host' not in newmessage['details']:
newmessage['details'] = u''
newmessage[u'summary'] = (
u'Found {software_type} '
u'name {name} '
u'on {host}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'socks':
if 'version' not in newmessage['details']:
newmessage['details'][u'version'] = u'0'
if 'status' not in newmessage['details']:
newmessage['details'][u'status'] = u'unknown'
newmessage[u'summary'] = (
u'SOCKSv{version}: '
u'{sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'status {status}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'dcerpc':
if 'endpoint' not in newmessage['details']:
newmessage['details'][u'endpoint'] = u'unknown'
if 'operation' not in newmessage['details']:
newmessage['details'][u'operation'] = u'unknown'
newmessage[u'summary'] = (
u'DCERPC: {sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'endpoint {endpoint} '
u'operation {operation}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'kerberos':
if 'request_type' not in newmessage['details']:
newmessage['details'][u'request_type'] = u'unknown'
if 'client' not in newmessage['details']:
newmessage['details'][u'client'] = u'unknown'
if 'service' not in newmessage['details']:
newmessage['details'][u'service'] = u'unknown'
if 'success' not in newmessage['details']:
newmessage['details'][u'success'] = u'unknown'
if 'error_msg' not in newmessage['details']:
newmessage['details'][u'error_msg'] = u''
newmessage[u'summary'] = (
u'{sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'client {client} '
u'request {request_type} '
u'service {service} '
u'success {success} '
u'{error_msg}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'ntlm':
if 'ntlmdomainname' not in newmessage['details']:
newmessage['details'][u'ntlmdomainname'] = u'unknown'
if 'ntlmhostname' not in newmessage['details']:
newmessage['details'][u'ntlmhostname'] = u'unknown'
if 'ntlmusername' not in newmessage['details']:
newmessage['details'][u'ntlmusername'] = u'unknown'
if 'success' not in newmessage['details']:
newmessage['details'][u'success'] = u'unknown'
if 'status' not in newmessage['details']:
newmessage['details'][u'status'] = u'unknown'
newmessage[u'summary'] = (
u'NTLM: {sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'user {ntlmusername} '
u'host {ntlmhostname} '
u'domain {ntlmdomainname} '
u'success {success} '
u'status {status}'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'smbfiles':
if 'path' not in newmessage['details']:
newmessage['details'][u'path'] = u''
if 'name' not in newmessage['details']:
newmessage['details'][u'name'] = u''
if 'action' not in newmessage['details']:
newmessage['details'][u'action'] = u''
newmessage[u'summary'] = (
'SMB file: '
u'{sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'{action}'
).format(**newmessage['details'])
return(newmessage, metadata)
if logtype == 'smbmapping':
if 'share_type' not in newmessage['details']:
newmessage['details'][u'share_type'] = u''
if 'path' not in newmessage['details']:
newmessage['details'][u'path'] = u''
newmessage[u'summary'] = (
'SMB mapping: '
u'{sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'{share_type}'
).format(**newmessage['details'])
return(newmessage, metadata)
if logtype == 'snmp':
if 'version' not in newmessage['details']:
newmessage['details'][u'version'] = u'Unknown'
if 'get_bulk_requests' not in newmessage['details']:
newmessage['details']['get_bulk_requests'] = 0
if 'get_requests' not in newmessage['details']:
newmessage['details']['get_requests'] = 0
if 'set_requests' not in newmessage['details']:
newmessage['details']['set_requests'] = 0
if 'get_responses' not in newmessage['details']:
newmessage['details']['get_responses'] = 0
newmessage['details']['getreqestssum'] = u'{0}'.format(newmessage['details']['get_bulk_requests'] + newmessage['details']['get_requests'])
newmessage[u'summary'] = (
u'SNMPv{version}: '
u'{sourceipaddress} -> '
u'{destinationipaddress}:'
u'{destinationport} '
u'({getreqestssum} get / '
u'{set_requests} set requests '
u'{get_responses} get responses)'
).format(**newmessage['details'])
return (newmessage, metadata)
if logtype == 'x509':
if 'certificateserial' not in newmessage['details']:
newmessage['details'][u'certificateserial'] = u'0'
newmessage[u'summary'] = (
'Certificate seen serial {certificateserial}'
).format(**newmessage['details'])
return (newmessage, metadata)
return (newmessage, metadata)

Просмотреть файл

Просмотреть файл

@ -1,42 +1,42 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Copyright (c) 2017 Mozilla Corporation
#
# Contributors:
# Arzhel Younsi arzhel@mozilla.com
# Jeff Bryner jbryner@mozilla.com
# Brandon Myers bmyers@mozilla.com
import os
from configlib import getConfig
class message(object):
def __init__(self):
'''
this plugin takes a source hostname of form
host.private.site.mozilla.com
extracts the site, adds it and compares the site
to a list of known datacenters or offices and adds that metadata
'''
self.registration = ['network', 'netflow']
self.priority = 5
config_location = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mozillaLocation.conf")
self.dc_code_list = getConfig('dc_code_list', '', config_location).split(',')
self.offices_code_list = getConfig('offices_code_list', '', config_location).split(',')
def onMessage(self, message, metadata):
if 'details' in message.keys() and 'hostname' in message['details'].keys():
hostnamesplit = str.lower(message['details']['hostname'].encode('ascii', 'ignore')).split('.')
if len(hostnamesplit) == 5:
if 'mozilla' == hostnamesplit[-2]:
message['details']['site'] = hostnamesplit[-3]
if message['details']['site'] in self.dc_code_list:
message['details']['sitetype'] = 'datacenter'
elif message['details']['site'] in self.offices_code_list:
message['details']['sitetype'] = 'office'
else:
message['details']['sitetype'] = 'unknown'
return (message, metadata)
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Copyright (c) 2017 Mozilla Corporation
#
# Contributors:
# Arzhel Younsi arzhel@mozilla.com
# Jeff Bryner jbryner@mozilla.com
# Brandon Myers bmyers@mozilla.com
import os
from configlib import getConfig
class message(object):
def __init__(self):
'''
this plugin takes a source hostname of form
host.private.site.mozilla.com
extracts the site, adds it and compares the site
to a list of known datacenters or offices and adds that metadata
'''
self.registration = ['network', 'netflow']
self.priority = 5
config_location = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mozilla_location.conf")
self.dc_code_list = getConfig('dc_code_list', '', config_location).split(',')
self.offices_code_list = getConfig('offices_code_list', '', config_location).split(',')
def onMessage(self, message, metadata):
if 'details' in message.keys() and 'hostname' in message['details'].keys():
hostnamesplit = str.lower(message['details']['hostname'].encode('ascii', 'ignore')).split('.')
if len(hostnamesplit) == 5:
if 'mozilla' == hostnamesplit[-2]:
message['details']['site'] = hostnamesplit[-3]
if message['details']['site'] in self.dc_code_list:
message['details']['sitetype'] = 'datacenter'
elif message['details']['site'] in self.offices_code_list:
message['details']['sitetype'] = 'office'
else:
message['details']['sitetype'] = 'unknown'
return (message, metadata)

101
rest/plugins/cymon.py Normal file
Просмотреть файл

@ -0,0 +1,101 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Copyright (c) 2014 Mozilla Corporation
#
# Contributors:
# Jeff Bryner jbryner@mozilla.com
import requests
import json
import os
import sys
from configlib import getConfig, OptionParser
class message(object):
def __init__(self):
'''register our criteria for being passed a message
as a list of lower case strings to match with an rest endpoint
(i.e. blockip matches /blockip)
set the priority if you have a preference for order of plugins
0 goes first, 100 is assumed/default if not sent
Plugins will register in Meteor with attributes:
name: (as below)
description: (as below)
priority: (as below)
file: "plugins.filename" where filename.py is the plugin code.
Plugin gets sent main rest options as:
self.restoptions
self.restoptions['configfile'] will be the .conf file
used by the restapi's index.py file.
'''
self.registration = ['ipintel']
self.priority = 5
self.name = "cymon"
self.description = "IP intel from the cymon.io api"
# set my own conf file
# relative path to the rest index.py file
self.configfile = './plugins/cymon.conf'
self.options = None
if os.path.exists(self.configfile):
sys.stdout.write('found conf file {0}\n'.format(self.configfile))
self.initConfiguration()
def onMessage(self, request, response):
'''
request: http://bottlepy.org/docs/dev/api.html#the-request-object
response: http://bottlepy.org/docs/dev/api.html#the-response-object
'''
if request.body:
arequest = request.body.read()
request.body.close()
try:
requestDict = json.loads(arequest)
except ValueError as e:
response.status = 500
print(requestDict, requestDict.keys())
if 'ipaddress' in requestDict.keys():
url="https://cymon.io/api/nexus/v1/ip/{0}/events?combined=true&format=json".format(requestDict['ipaddress'])
# add the cymon api key?
if self.options is not None:
headers = {'Authorization': 'Token {0}'.format(self.options.cymonapikey)}
else:
headers = None
dresponse = requests.get(url, headers=headers)
if dresponse.status_code == 200:
response.content_type = "application/json"
response.body = dresponse.content
response.status=200
else:
response.status = dresponse.status_code
else:
response.status = 500
return (request, response)
def initConfiguration(self):
myparser = OptionParser()
# setup self.options by sending empty list [] to parse_args
(self.options, args) = myparser.parse_args([])
# fill self.options with plugin-specific options
# cymon options
self.options.cymonapikey = getConfig('cymonapikey',
'',
self.configfile)

Просмотреть файл

@ -64,7 +64,7 @@ class message(object):
# set my own conf file
# relative path to the rest index.py file
self.configfile = './plugins/facebookThreatExchange.conf'
self.configfile = './plugins/facebook_threatexchange.conf'
self.options = None
if os.path.exists(self.configfile):
sys.stdout.write('found conf file {0}\n'.format(self.configfile))

Просмотреть файл

Просмотреть файл

@ -43,18 +43,17 @@ class message(object):
(i.e. blockip matches /blockip)
set the priority if you have a preference for order of plugins
0 goes first, 100 is assumed/default if not sent
Plugins will register in Meteor with attributes:
name: (as below)
description: (as below)
priority: (as below)
file: "plugins.filename" where filename.py is the plugin code.
Plugin gets sent main rest options as:
self.restoptions
self.restoptions['configfile'] will be the .conf file
used by the restapi's index.py file.
'''
self.registration = ['blockip']
@ -64,13 +63,12 @@ class message(object):
# set my own conf file
# relative path to the rest index.py file
self.configfile = './plugins/vpcblackhole.conf'
self.configfile = './plugins/vpc_blackhole.conf'
self.options = None
self.multioptions = []
if os.path.exists(self.configfile):
sys.stdout.write('found conf file {0}\n'.format(self.configfile))
self.initConfiguration()
def initConfiguration(self):
myparser = ConfigParser.ConfigParser()
@ -81,7 +79,7 @@ class message(object):
cur_options = myparser.options(cur_section)
if cur_options is not None:
self.multioptions.append({ 'region': myparser.get(cur_section, 'region'), 'aws_access_key_id': myparser.get(cur_section, 'aws_access_key_id'), 'aws_secret_access_key': myparser.get(cur_section, 'aws_secret_access_key') } )
def addBlackholeEntry(self,
ipaddress=None):
try:
@ -154,7 +152,7 @@ class message(object):
else:
sys.stdout.write('Skipping route table {0} in the VPC {1} - blackhole ENI could not be found\n'.format(rt_id, vpc_id))
continue
except Exception as e:
sys.stderr.write('Error while creating a blackhole entry %s: %r\n' % (ipaddress, e))
@ -163,16 +161,16 @@ class message(object):
'''
request: http://bottlepy.org/docs/dev/api.html#the-request-object
response: http://bottlepy.org/docs/dev/api.html#the-response-object
'''
'''
# format/validate request.json:
ipaddress = None
CIDR = None
sendToBHVPC = False
# loop through the fields of the form
# and fill in our values
try:
try:
for i in request.json:
# were we checked?
if self.name in i.keys():
@ -197,5 +195,5 @@ class message(object):
sys.stdout.write ('Blackholed {0}\n'.format(ipaddress))
except Exception as e:
sys.stderr.write('Error handling request.json %r \n'% (e))
return (request, response)

Просмотреть файл

@ -1,5 +1,5 @@
[Unit]
Description=uWSGI mozdefloginput
Description=uWSGI MozDef Loginput Service
After=rabbitmq-server.service
[Service]

Просмотреть файл

@ -1,5 +1,5 @@
[Unit]
Description=uWSGI MozDef Log Input Service
Description=uWSGI MozDef Loginput Service
After=rabbitmq-server.service
[Service]

Просмотреть файл

@ -1,5 +1,5 @@
[Unit]
Description=uWSGI eventtask mworker
Description=uWSGI MozDef Eventtask mworker Service
After=rabbitmq-server.service
[Service]
@ -9,7 +9,7 @@ ExecStartPre=-/usr/bin/mkdir -p /var/run/mozdef_mq_worker_pids
ExecStartPre=/usr/bin/chown -R mozdef:mozdef /var/run/mozdef_mq_worker_pids
User=mozdef
Group=mozdef
ExecStart=/bin/bash -c 'cd /opt/mozdef/envs/mozdef/mq/; source /opt/mozdef/envs/mozdef/bin/activate; uwsgi --ini eventtask.ini'
ExecStart=/bin/bash -c 'cd /opt/mozdef/envs/mozdef/mq; source /opt/mozdef/envs/mozdef/bin/activate; uwsgi --ini eventtask.ini'
Restart=always
KillSignal=SIGQUIT
Type=notify
@ -18,4 +18,5 @@ NotifyAccess=all
[Install]
WantedBy=multi-user.target
Alias=eventtask
Alias=eventtask

Просмотреть файл

@ -1,5 +1,5 @@
[Unit]
Description=uWSGI mozdef restapi
Description=uWSGI MozDef Rest API Service
After=mozdefweb.service
[Service]

Просмотреть файл

@ -0,0 +1,117 @@
from positive_alert_test_case import PositiveAlertTestCase
from negative_alert_test_case import NegativeAlertTestCase
from alert_test_suite import AlertTestSuite
class TestSSHKey(AlertTestSuite):
alert_filename = 'ssh_key'
alert_classname = 'SSHKey'
# This event is the default positive event that will cause the
# alert to trigger
default_event = {
'_type': 'event',
'_source': {
'category': 'event',
'processid': '19690',
'severity': 'INFO',
'tags': [
'mig-runner-sshkey'
],
'hostname': 'mig-runner-host.mozilla.com',
'mozdefhostname': 'mozdef-host.mozilla.com',
'summary': 'MIG sshkey module result for key-host.mozilla.com',
'processname': '/home/migrunner/runner/plugins/sshkeyplugin.py',
'details': {
'authorizedkeys': [
{
'fingerprint_sha256': 'SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'encrypted': False,
'fingerprint_md5': '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00',
'path': '/home/user/.ssh/authorized_keys'
}
],
'private': [
{
'fingerprint_sha256': 'SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'encrypted': False,
'fingerprint_md5': '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00',
'path': '/home/user/.ssh/id_rsa'
},
{
'fingerprint_sha256': 'SHA256:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB',
'encrypted': False,
'fingerprint_md5': '11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11',
'path': '/home/user2/.ssh/id_rsa'
}
],
'agent': 'key-host.mozilla.com',
}
}
}
# This alert is the expected result from running this task
default_alert = {
'category': 'sshkey',
'severity': 'WARNING',
'summary': 'Private keys detected on key-host.mozilla.com missing from whitelist',
'tags': ['sshkey'],
}
test_cases = []
test_cases.append(
PositiveAlertTestCase(
description='Positive test case with good event',
events=[AlertTestSuite.create_event(default_event)],
expected_alert=default_alert
)
)
event = AlertTestSuite.create_event(default_event)
event['_type'] = 'audit'
test_cases.append(
NegativeAlertTestCase(
description="Negative test case with bad event type",
events=[event],
)
)
event = AlertTestSuite.create_event(default_event)
event['_source']['details']['agent'] = 'somehost.ignorehosts.com'
event['_source']['details']['private'] = [
{
'fingerprint_sha256': 'SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'encrypted': False,
'fingerprint_md5': '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00',
'path': '/home/user/.ssh/id_rsa'
}
]
test_cases.append(
NegativeAlertTestCase(
description='Whitelist test with default configuration file',
events=[event],
)
)
event = AlertTestSuite.create_event(default_event)
event['_source']['details']['agent'] = 'somehost.ignorehosts.com'
event['_source']['details']['private'] = [
{
'fingerprint_sha256': 'SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'encrypted': False,
'fingerprint_md5': '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00',
'path': '/home/user2/.ssh/id_rsa'
}
]
specific_alert = default_alert.copy()
specific_alert['summary'] = 'Private keys detected on somehost.ignorehosts.com missing from whitelist'
test_cases.append(
PositiveAlertTestCase(
description='Whitelist test with default configuration file, only host matches',
events=[event],
expected_alert=specific_alert
)
)

Просмотреть файл

@ -0,0 +1,75 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Copyright (c) 2017 Mozilla Corporation
from positive_alert_test_case import PositiveAlertTestCase
from negative_alert_test_case import NegativeAlertTestCase
from alert_test_suite import AlertTestSuite
class TestAlertSSHPasswordAuthViolation(AlertTestSuite):
alert_filename = "ssh_password_auth_violation"
# This event is the default positive event that will cause the
# alert to trigger
default_event = {
"_type": "event",
"_source": {
"tags": ["ssh_password_auth_policy_violation"],
"details": {
"destinationipaddress": "1.2.3.4",
"destinationport": 22,
}
}
}
# This alert is the expected result from running this task
default_alert = {
"category": "ssh_password_auth_policy_violation",
"tags": ['ssh_password_auth_policy_violation'],
"severity": "WARNING",
"summary": 'SSH password authentication allowed on 1.2.3.4',
}
test_cases = []
test_cases.append(
PositiveAlertTestCase(
description="Positive test with default events and default alert expected",
events=AlertTestSuite.create_events(default_event, 1),
expected_alert=default_alert
)
)
events = AlertTestSuite.create_events(default_event, 10)
for event in events:
event['_type'] = 'bad'
test_cases.append(
NegativeAlertTestCase(
description="Negative test case with events with incorrect _type",
events=events,
)
)
events = AlertTestSuite.create_events(default_event, 10)
for event in events:
event['_source']['tags'] = 'bad tag example'
test_cases.append(
NegativeAlertTestCase(
description="Negative test case with events with incorrect tags",
events=events,
)
)
events = AlertTestSuite.create_events(default_event, 10)
for event in events:
event['_source']['utctimestamp'] = AlertTestSuite.subtract_from_timestamp_lambda({'minutes': 241})
event['_source']['receivedtimestamp'] = AlertTestSuite.subtract_from_timestamp_lambda({'minutes': 241})
test_cases.append(
NegativeAlertTestCase(
description="Negative test case with old timestamp",
events=events,
)
)

Разница между файлами не показана из-за своего большого размера Загрузить разницу