Guest User

suricata_extractor.py

a guest
May 13th, 2020
138
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 28.93 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3.  
  4. # See the file 'LICENSE' for copying permission.
  5. # Author: Sebastian Garcia. [email protected] , [email protected]
  6.  
  7. import sys
  8. from datetime import datetime
  9. from datetime import timedelta
  10. import argparse
  11. import time
  12. from os.path import isfile, join
  13. import json
  14. from pprint import pprint
  15. import matplotlib.pyplot as plt
  16. import matplotlib.patches as mpatches
  17. import math
  18. import subprocess
  19.  
  20. version = '0.3.2'
  21.  
  22. # Changelog
  23. # 0.3.1
  24. #  Output information for each combination of ports accessed by each uniq attacker
  25. # 0.3.1:
  26. #  Delete the export in csv
  27. #  Only suricata categories with data is exported in the json
  28. #  Export in json
  29. #  Add summary of alerts per dst port
  30. # 0.3:
  31. #  Fix the Unknown category in the csv
  32. # 0.2:
  33. #  Generate the csv
  34. #  Plot the data
  35. #  Generate an image file
  36.  
  37. timewindows = {}
  38. timeStampFormat = '%Y-%m-%dT%H:%M:%S.%f'
  39. categories = {
  40.     'Not Suspicious Traffic': [],
  41.     'Unknown Traffic': [],
  42.     'Potentially Bad Traffic': [],
  43.     'Attempted Information Leak': [],
  44.     'Information Leak': [],
  45.     'Large Scale Information Leak': [],
  46.     'Attempted Denial of Service': [],
  47.     'Denial of Service': [],
  48.     'Attempted User Privilege Gain': [],
  49.     'Unsuccessful User Privilege Gain': [],
  50.     'Successful User Privilege Gain': [],
  51.     'Attempted Administrator Privilege Gain': [],
  52.     'Successful Administrator Privilege Gain': [],
  53.     'Decode of an RPC Query': [],
  54.     'Executable Code was Detected': [],
  55.     'A Suspicious String was Detected': [],
  56.     'A Suspicious Filename was Detected': [],
  57.     'An Attempted Login Using a Suspicious Username was Detected': [],
  58.     'A System Call was Detected': [],
  59.     'A TCP Connection was Detected': [],
  60.     'A Network Trojan was Detected': [],
  61.     'A Client was Using an Unusual Port': [],
  62.     'Detection of a Network Scan': [],
  63.     'Detection of a Denial of Service Attack': [],
  64.     'Detection of a Non-Standard Protocol or Event': [],
  65.     'Generic Protocol Command Decode': [],
  66.     'Access to a Potentially Vulnerable Web Application': [],
  67.     'Web Application Attack': [],
  68.     'Misc activity': [],
  69.     'Misc Attack': [],
  70.     'Generic ICMP event': [],
  71.     'Inappropriate Content was Detected': [],
  72.     'Potential Corporate Privacy Violation': [],
  73.     'Attempt to Login By a Default Username and Password': [],
  74.     }
  75. colors = [
  76.     '#d6d6f5',
  77.     '#7070db',
  78.     '#24248f',
  79.     '#ffccff',
  80.     '#ff1aff',
  81.     '#990099',
  82.     '#ffb3d1',
  83.     '#ff0066',
  84.     '#99003d',
  85.     '#ffe6b3',
  86.     '#ffcc66',
  87.     '#ffaa00',
  88.     '#ffffb3',
  89.     '#ffff00',
  90.     '#cccc00',
  91.     '#d9ffb3',
  92.     '#99ff33',
  93.     '#66cc00',
  94.     '#c6ecd9',
  95.     '#66cc99',
  96.     '#2d8659',
  97.     '#c2f0f0',
  98.     '#47d1d1',
  99.     '#248f8f',
  100.     '#b3f0ff',
  101.     '#00ccff',
  102.     '#008fb3',
  103.     '#b3ccff',
  104.     '#6699ff',
  105.     '#004de6',
  106.     '#ffccff',
  107.     '#ff66ff',
  108.     '#b300b3',
  109.     '#ffb3d1',
  110.     ]
  111.  
  112.  
  113. ###################
  114. # TimeWindow
  115.  
  116. class TimeWindow(object):
  117.  
  118.     """ Store info about the time window """
  119.  
  120.     def __init__(self, hourstring):
  121.         self.hour = hourstring
  122.         self.start_time = hourstring
  123.         self.categories = {}
  124.         self.severities = {}
  125.         self.severities[1] = 0
  126.         self.severities[2] = 0
  127.         self.severities[3] = 0
  128.         self.severities[4] = 0
  129.         self.signatures = {}
  130.         self.src_ips = {}
  131.         self.dst_ips = {}
  132.         self.src_ports = {}
  133.         self.dst_ports = {}
  134.  
  135.         # port_combinations will be: {dstip: {srcip: [1st port, 2nd port]}}
  136.  
  137.         self.port_combinations = {}
  138.         self.final_count_per_dst_ip = {}
  139.  
  140.         # bandwidth = {dstport: [mbits]}
  141.  
  142.         self.bandwidth = {}
  143.  
  144.     def add_flow(
  145.         self,
  146.         src_ip,
  147.         dst_ip,
  148.         srcport,
  149.         dstport,
  150.         proto,
  151.         bytes_toserver,
  152.         bytes_toclient,
  153.         ):
  154.         """
  155.        Receive a flow and use it
  156.        """
  157.  
  158.         # If we were told to get the bandwidth, do ti
  159.  
  160.         if args.bandwidth:
  161.             if 'TCP' in proto:
  162.                 try:
  163.                     data = self.bandwidth[dstport]
  164.                     self.bandwidth[dstport] += bytes_toserver \
  165.                         + bytes_toclient
  166.                 except KeyError:
  167.                     self.bandwidth[dstport] = bytes_toserver \
  168.                         + bytes_toclient
  169.  
  170.     def add_alert(
  171.         self,
  172.         category,
  173.         severity,
  174.         signature,
  175.         src_ip,
  176.         dst_ip,
  177.         srcport,
  178.         destport,
  179.         ):
  180.         """
  181.        Receive an alert and it adds it to the TW
  182.        """
  183.  
  184.         # Categories
  185.  
  186.         if args.debug > 1:
  187.             print '\ncat:{}, sev:{}, sig:{}, srcip:{}, dstip:{}, srcp:{}, dstp:{}'.format(
  188.                 category,
  189.                 severity,
  190.                 signature,
  191.                 src_ip,
  192.                 dst_ip,
  193.                 srcport,
  194.                 destport,
  195.                 )
  196.         if category == '':
  197.             try:
  198.                 self.categories['Unknown Traffic'] += 1
  199.             except KeyError:
  200.                 self.categories['Unknown Traffic'] = 1
  201.         else:
  202.             try:
  203.                 self.categories[category] += 1
  204.             except KeyError:
  205.                 self.categories[category] = 1
  206.  
  207.         # Severities
  208.  
  209.         try:
  210.             self.severities[int(severity)] += 1
  211.         except KeyError:
  212.             self.severities[int(severity)] = 1
  213.  
  214.         # Signatures
  215.  
  216.         try:
  217.             self.signatures[signature] += 1
  218.         except KeyError:
  219.             self.signatures[signature] = 1
  220.  
  221.         # Srcip
  222.  
  223.         try:
  224.             self.src_ips[src_ip] += 1
  225.         except KeyError:
  226.             self.src_ips[src_ip] = 1
  227.  
  228.         # Dstip
  229.  
  230.         try:
  231.             self.dst_ips[dst_ip] += 1
  232.         except KeyError:
  233.             self.dst_ips[dst_ip] = 1
  234.  
  235.         # Srcport
  236.  
  237.         try:
  238.             self.src_ports[srcport] += 1
  239.         except KeyError:
  240.             self.src_ports[srcport] = 1
  241.  
  242.         # dstport
  243.  
  244.         try:
  245.             self.dst_ports[destport] += 1
  246.         except KeyError:
  247.             self.dst_ports[destport] = 1
  248.  
  249.         # Compute the combination of ports per unique attacker
  250.         # port_combinations will be: {dstip: {srcip: [ 1stport, 2ndport ]}}
  251.         # Do not do it if the dest port is empty (in icmp for example), and if the dst port is not in the list of the ports we want to monitor.
  252.         # The list of ports to monitor is the list of usual low numbered ports. Because suricata can give alerts for packets going back, so the dst port is 65332. We don't want that.
  253.  
  254.         if args.ports and destport != '' \
  255.             and interesting_ports.has_key(destport):
  256.             try:
  257.                 srcdict = self.port_combinations[dst_ip]
  258.                 try:
  259.  
  260.                     # the dstip is there, the srcip is also there, just add the port
  261.  
  262.                     ports = srcdict[src_ip]
  263.  
  264.                     # We have this dstip, srcip, just add the port
  265.  
  266.                     try:
  267.                         ports.index(destport)
  268.                     except ValueError:
  269.                         ports.append(destport)
  270.                     srcdict[src_ip] = ports
  271.                     self.port_combinations[dst_ip] = srcdict
  272.                     if args.debug:
  273.                         print 'Added port {}, to srcip {} attacking dstip {}'.format(destport,
  274.                                 src_ip, dst_ip)
  275.                 except KeyError:
  276.  
  277.                     # first time for this src_ip attacking this dst_ip
  278.  
  279.                     ports = []
  280.                     ports.append(destport)
  281.                     srcdict[src_ip] = ports
  282.                     self.port_combinations[dst_ip] = srcdict
  283.                     if args.debug:
  284.                         print 'New srcip {} attacking dstip {} on port {}'.format(src_ip,
  285.                                 dst_ip, destport)
  286.             except KeyError:
  287.  
  288.                 # First time for this dst ip
  289.  
  290.                 ports = []
  291.                 ports.append(destport)
  292.                 srcdict = {}
  293.                 srcdict[src_ip] = ports
  294.                 self.port_combinations[dst_ip] = srcdict
  295.                 if args.debug:
  296.                     print 'New dst IP {}, attacked from srcip {} on port {}'.format(dst_ip,
  297.                             src_ip, destport)
  298.  
  299.     def get_json(self):
  300.         """
  301.        Returns the json representation of the data in this time window
  302.        """
  303.  
  304.         data = {}
  305.         data['Alerts Categories'] = self.categories
  306.         data['# Uniq Signatures'] = len(self.signatures)
  307.         data['# Severity 1'] = \
  308.             self.severities[self.severities.keys()[0]]
  309.         data['# Severity 2'] = \
  310.             self.severities[self.severities.keys()[1]]
  311.         data['# Severity 3'] = \
  312.             self.severities[self.severities.keys()[2]]
  313.         data['# Severity 4'] = \
  314.             self.severities[self.severities.keys()[3]]
  315.         data['Alerts/DstPort'] = self.dst_ports
  316.  
  317.         # data['Alerts/SrcPort'] = self.src_ports
  318.  
  319.         data['Alerts/SrcIP'] = self.src_ips
  320.         data['Alers/DstIP'] = self.dst_ips
  321.         result = {}
  322.         result[self.hour] = data
  323.  
  324.         # data['Per SrcPort'] = self.src_ports
  325.  
  326.         json_result = json.dumps(result)
  327.         return json_result
  328.  
  329.     def count_port_combinations(self):
  330.         """
  331.        Compute the amount of attackers attacking each port combination on each dst ip
  332.        """
  333.  
  334.         self.final_count_per_dst_ip = {}
  335.         final_ports_counts = {}
  336.         for dst_ip in self.port_combinations:
  337.             for src_ip in self.port_combinations[dst_ip]:
  338.  
  339.                 # We count precisely who attacks ports 22,80, ... no 22,80,443 as also 22,80
  340.  
  341.                 portscom = \
  342.                     str(self.port_combinations[dst_ip][src_ip]).replace('['
  343.                         , '').replace(']', '')
  344.                 try:
  345.                     amount = final_ports_counts[portscom]
  346.                     amount += 1
  347.                     final_ports_counts[portscom] = amount
  348.                 except KeyError:
  349.                     amount = 1
  350.                     final_ports_counts[portscom] = amount
  351.             self.final_count_per_dst_ip[dst_ip] = final_ports_counts
  352.             final_ports_counts = {}
  353.  
  354.     def print_port_combinations(self):
  355.         print self.final_count_per_dst_ip
  356.  
  357.         # for dst_ip in self.final_count_per_dst_ip:
  358.         #    print dst_ip
  359.         #    print '\t' + str(self.final_count_per_dst_ip[dst_ip])
  360.  
  361.     def get_port_combination_lines(self):
  362.         """
  363.        Call the combination of ports and return an object with all the info for this TW.
  364.        """
  365.  
  366.         self.count_port_combinations()
  367.         return self.final_count_per_dst_ip
  368.  
  369.     def __repr__(self):
  370.         return 'TW: {}. #Categories: {}. #Signatures: {}. #SrcIp: {}. #DstIP: {}. #Severities: 1:{}, 2:{}, 3:{}, 4:{}'.format(
  371.             str(self.hour),
  372.             len(self.categories),
  373.             len(self.signatures),
  374.             len(self.src_ips),
  375.             len(self.dst_ips),
  376.             self.severities[self.severities.keys()[0]],
  377.             self.severities[self.severities.keys()[1]],
  378.             self.severities[self.severities.keys()[2]],
  379.             self.severities[self.severities.keys()[3]],
  380.             )
  381.  
  382.     def printit(self):
  383.         print 'TW: {}. #Categories: {}. #Signatures: {}. #SrcIp: {}. #DstIP: {}. #Severities: 1:{}, 2:{}, 3:{}, 4:{}'.format(
  384.             str(self.hour),
  385.             len(self.categories),
  386.             len(self.signatures),
  387.             len(self.src_ips),
  388.             len(self.dst_ips),
  389.             self.severities[self.severities.keys()[0]],
  390.             self.severities[self.severities.keys()[1]],
  391.             self.severities[self.severities.keys()[2]],
  392.             self.severities[self.severities.keys()[3]],
  393.             )
  394.  
  395.  
  396. def get_tw(col_time):
  397.     """
  398.    Creates the time window or get the correct one.
  399.    When a TW is finished here we should call the output function.
  400.    """
  401.  
  402.     timestamp = datetime.strptime(col_time, timeStampFormat)
  403.  
  404.     # Get the closest down time rounded
  405.  
  406.     round_down_timestamp = roundTime(timestamp,
  407.             timedelta(minutes=args.width), 'down')
  408.     str_round_down_timestamp = \
  409.         round_down_timestamp.strftime(timeStampFormat)
  410.     try:
  411.         tw = timewindows[str_round_down_timestamp]
  412.         if args.verbose > 3:
  413.             print 'Getting an old tw {}'.format(tw)
  414.     except KeyError:
  415.  
  416.         # New tw
  417.         # Get the previous TW id
  418.  
  419.         prev_tw_date = round_down_timestamp \
  420.             - timedelta(minutes=int(args.width))
  421.         str_prev_round_down_timestamp = \
  422.             prev_tw_date.strftime(timeStampFormat)
  423.         output_tw(str_prev_round_down_timestamp)
  424.         tw = TimeWindow(str_round_down_timestamp)
  425.         tw.set_start_time = timestamp
  426.         timewindows[str_round_down_timestamp] = tw
  427.         if args.verbose > 2:
  428.             print 'New tw created at {}'.format(str_round_down_timestamp)
  429.     return tw
  430.  
  431.  
  432. def output_tw(time_tw):
  433.     """
  434.    Print the TW in screen
  435.    Output the tw in files
  436.    """
  437.  
  438.     try:
  439.         tw = timewindows[time_tw]
  440.         if args.verbose > 1:
  441.             print 'Printing TW that started in: {}'.format(time_tw)
  442.         tw.printit()
  443.     except KeyError:
  444.         return False
  445.     print '\tCategories:'
  446.     for cat in tw.categories:
  447.         if tw.categories[cat] != 0:
  448.             print '\t\t{}: {}'.format(cat, tw.categories[cat])
  449.  
  450.     # Json
  451.  
  452.     if args.json:
  453.         jsonline = tw.get_json()
  454.         jsonfile.write(jsonline + '\n')
  455.         jsonfile.flush()
  456.  
  457.     # Ports combination file
  458.  
  459.     if args.ports:
  460.         portslines = tw.get_port_combination_lines()
  461.         portsfile.write(str(tw.hour) + '\n')
  462.         for dst_ip in portslines:
  463.             portsfile.write(str(dst_ip) + ': '
  464.                             + str(portslines[dst_ip]) + '\n')
  465.         portsfile.flush()
  466.  
  467.     # flows
  468.  
  469.     if args.bandwidth:
  470.         print '\tDports Bandwidth'
  471.         for dport in tw.bandwidth:
  472.             mbits = float(tw.bandwidth[dport]) / 8.0 / (args.width * 60)
  473.             print '\t\t{}: {} Mbit/s'.format(dport, mbits)
  474.  
  475.  
  476. def plot():
  477.     """
  478.    """
  479.  
  480.     if args.verbose > 1:
  481.         print 'Plotting {} timewindows'.format(len(timewindows))
  482.     plt.figure(figsize=(10, 3))
  483.     plt.subplots_adjust(right=0.75)
  484.     if args.verbose > 1:
  485.         print 'Figure created'
  486.     cat1val = []
  487.     cat2val = []
  488.     sev1val = []
  489.     sev2val = []
  490.     sev3val = []
  491.     sev4val = []
  492.     sigval = []
  493.     srcipval = []
  494.     dstipval = []
  495.     categoriesvals = []
  496.  
  497.     # Scale
  498.  
  499.     if args.log:
  500.  
  501.         # yfunc = lambda y: map(lambda x:math.log(x), y)
  502.  
  503.         def yfunc(y):
  504.             v = []
  505.             for i in y:
  506.                 if i == 0:
  507.                     v.append(0)
  508.                 else:
  509.                     v.append(math.log(i))
  510.             return v
  511.  
  512.     elif not args.log:
  513.         yfunc = lambda y: y
  514.  
  515.     # labels = []
  516.  
  517.     if args.verbose > 1:
  518.         print 'Going through the time windows to fill the data'
  519.     for tw in sorted(timewindows.iterkeys()):
  520.  
  521.         # labels.append(tw)
  522.  
  523.         for cat in categories:
  524.             try:
  525.                 categories[cat].append(timewindows[tw].categories[cat])
  526.             except KeyError:
  527.                 categories[cat].append(0)
  528.         sev1val.append(timewindows[tw].severities[1])
  529.         sev2val.append(timewindows[tw].severities[2])
  530.         sev3val.append(timewindows[tw].severities[3])
  531.         sev4val.append(timewindows[tw].severities[4])
  532.         sigval.append(len(timewindows[tw].signatures))
  533.         srcipval.append(len(timewindows[tw].src_ips))
  534.         dstipval.append(len(timewindows[tw].dst_ips))
  535.     y = range(1, len(timewindows) + 1)
  536.     if args.verbose > 1:
  537.         print 'Creating the plot with the data'
  538.     index = 0
  539.     while index < len(categories):
  540.         cat = categories.items()[index][0]
  541.         values = categories.items()[index][1]
  542.         if sum(values) == 0:
  543.             index += 1
  544.             continue
  545.         plt.plot(y, yfunc(values), linestyle='-', color=colors[index],
  546.                  label=cat)
  547.         index += 1
  548.     plt.plot(
  549.         y,
  550.         yfunc(sev1val),
  551.         color='#c48efd',
  552.         marker='o',
  553.         linestyle='',
  554.         label='Severity 1',
  555.         )
  556.     plt.plot(
  557.         y,
  558.         yfunc(sev2val),
  559.         color='#507b9c',
  560.         marker='<',
  561.         linestyle='',
  562.         label='Severity 2',
  563.         )
  564.     plt.plot(
  565.         y,
  566.         yfunc(sev3val),
  567.         color='#6b7c85',
  568.         marker='+',
  569.         linestyle='',
  570.         label='Severity 3',
  571.         )
  572.     plt.plot(
  573.         y,
  574.         yfunc(sev4val),
  575.         color='#009337',
  576.         marker='*',
  577.         linestyle='',
  578.         label='Severity 4',
  579.         )
  580.     plt.plot(y, yfunc(sigval), 'ms', label='Signatures')
  581.     plt.plot(y, yfunc(srcipval), 'y--', label='SrcIps')
  582.     plt.plot(y, yfunc(dstipval), 'k--', label='DstIps')
  583.  
  584.     # plt.legend(bbox_to_anchor=(1.05, 1), loc=2), borderaxespad=0.)
  585.     # plt.legend(bbox_to_anchor=(1.05, 1), loc=2)
  586.  
  587.     plt.legend(bbox_to_anchor=(1, 1), loc=2)
  588.  
  589.     # plt.xticks(range(1,len(labels)), labels)
  590.     # plt.legend(loc=0)
  591.  
  592.     if args.log:
  593.         plt.ylabel('Amount in Log scale)')
  594.     else:
  595.         plt.ylabel('Amount')
  596.  
  597.     # ylabs = [math.exp(i) for i in range(0,10)]
  598.     # plt.yticks(ylabs)
  599.  
  600.     if args.plotfile:
  601.         plt.savefig(args.plotfile, dpi=1000)
  602.     plt.show()
  603.  
  604.  
  605. def process_line(line):
  606.     """
  607.    Process each line, extract the columns, get the correct TW and store each alert on the TW object
  608.    """
  609.  
  610.     if args.verbose > 3:
  611.         print 'Processing line {}'.format(line)
  612.     try:
  613.         json_line = json.loads(line)
  614.     except ValueError:
  615.         print 'Error reading the json file'
  616.         return False
  617.  
  618.     if 'alert' not in json_line['event_type'] and 'flow' \
  619.         not in json_line['event_type']:
  620.         return False
  621.     if args.dstnet and args.dstnet not in json_line['dest_ip']:
  622.         return False
  623.     if args.verbose > 2:
  624.         print 'Accepted line {}'.format(line)
  625.  
  626.     # forget the timezone for now with split
  627.  
  628.     try:
  629.         col_time = json_line['timestamp'].split('+')[0]
  630.     except KeyError:
  631.         col_time = ''
  632.     try:
  633.         col_category = json_line['alert']['category']
  634.     except KeyError:
  635.         col_category = ''
  636.     try:
  637.         col_severity = json_line['alert']['severity']
  638.     except KeyError:
  639.         col_severity = ''
  640.     try:
  641.         col_signature = json_line['alert']['signature']
  642.     except KeyError:
  643.         col_signature = ''
  644.     try:
  645.         col_srcip = json_line['src_ip']
  646.     except KeyError:
  647.         col_srcip = ''
  648.     try:
  649.         col_dstip = json_line['dest_ip']
  650.     except KeyError:
  651.         col_dstip = ''
  652.     try:
  653.         col_srcport = json_line['src_port']
  654.     except KeyError:
  655.         col_srcport = ''
  656.     try:
  657.         col_dstport = json_line['dest_port']
  658.     except KeyError:
  659.         col_dstport = ''
  660.  
  661.     # Get the time window object
  662.  
  663.     current_tw = get_tw(col_time)
  664.     if 'alert' in json_line['event_type']:
  665.         current_tw.add_alert(
  666.             col_category,
  667.             col_severity,
  668.             col_signature,
  669.             col_srcip,
  670.             col_dstip,
  671.             col_srcport,
  672.             col_dstport,
  673.             )
  674.     elif 'flow' in json_line['event_type']:
  675.         try:
  676.             col_proto = json_line['proto']
  677.         except KeyError:
  678.             col_proto = ''
  679.         try:
  680.             col_bytes_toserver = json_line['flow']['bytes_toserver']
  681.         except KeyError:
  682.             col_bytes_toserver = ''
  683.         try:
  684.             col_bytes_toclient = json_line['flow']['bytes_toclient']
  685.         except KeyError:
  686.             col_bytes_toclient = ''
  687.         current_tw.add_flow(
  688.             col_srcip,
  689.             col_dstip,
  690.             col_srcport,
  691.             col_dstport,
  692.             col_proto,
  693.             col_bytes_toserver,
  694.             col_bytes_toclient,
  695.             )
  696.     return current_tw
  697.  
  698.  
  699. def roundTime(dt=None, date_delta=timedelta(minutes=1), to='average'):
  700.     """
  701.    Round a datetime object to a multiple of a timedelta
  702.    dt : datetime.datetime object, default now.
  703.    dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
  704.    from:  http://stackoverflow.com/questions/3463930/how-to-round-the-minute-of-a-datetime-object-python
  705.    """
  706.  
  707.     round_to = date_delta.total_seconds()
  708.     if dt is None:
  709.         dt = datetime.now()
  710.     seconds = (dt - dt.min).seconds
  711.     if to == 'up':
  712.  
  713.         # // is a floor division, not a comment on following line (like in javascript):
  714.  
  715.         rounding = (seconds + round_to) // round_to * round_to
  716.     elif to == 'down':
  717.         rounding = seconds // round_to * round_to
  718.     else:
  719.         rounding = (seconds + round_to / 2) // round_to * round_to
  720.     return dt + timedelta(0, rounding - seconds, -dt.microsecond)
  721.  
  722.  
  723. def summarize_ports():
  724.     """
  725.    After all the tw finished, summarize the port combinations in all the TW and print it in a separate file
  726.    """
  727.  
  728.     if args.debug > 0:
  729.         print 'Computing the summary of ports combinations in all the time windows'
  730.     port_summary = {}
  731.     for tw in timewindows:
  732.         ports_data = timewindows[tw].final_count_per_dst_ip
  733.         for srcip in ports_data:
  734.             try:
  735.  
  736.                 # print 'Src IP: {}'.format(srcip)
  737.                 # ports for this ip alredy in the global dict
  738.  
  739.                 srcip_ports = port_summary[srcip]
  740.  
  741.                 # print 'Ports com we already have: {}'.format(srcip_ports)
  742.                 # print 'Ports com in the current tw: {}'.format(ports_data[srcip])
  743.                 # for each port in the ports for the src ip in the current tw
  744.  
  745.                 for twport in ports_data[srcip]:
  746.                     try:
  747.  
  748.                         # is this combination of ports in the global dict?
  749.  
  750.                         amount = srcip_ports[twport]
  751.  
  752.                         # yes, so add the new ports
  753.  
  754.                         srcip_ports[twport] += ports_data[srcip][twport]
  755.                     except KeyError:
  756.  
  757.                         # print 'We do have this comb. Updating to {}'.format(srcip_ports)
  758.                         # The new port combination is not in the global dict yet, just store the ports we have in the current tw
  759.  
  760.                         srcip_ports[twport] = ports_data[srcip][twport]
  761.  
  762.                         # print 'We do not have this comb. Updating to {}'.format(srcip_ports)
  763.                 # update the global dict for this src ip
  764.  
  765.                 port_summary[srcip] = srcip_ports
  766.             except KeyError:
  767.                 port_summary[srcip] = ports_data[srcip]
  768.     summaryportsfilename = '.'.join(args.json.split('.')[:-1]) \
  769.         + '.summary_ports'
  770.     summary_port_file = open(summaryportsfilename, 'w')
  771.     for srcip in port_summary:
  772.         summary_port_file.write(str(srcip) + ': '
  773.                                 + str(port_summary[srcip]) + '\n')
  774.     summary_port_file.close()
  775.  
  776.  
  777. ####################
  778. # Main
  779. ####################
  780.  
  781. if __name__ == '__main__':
  782.     print 'Suricata Extractor. Version {} (https://stratosphereips.org)'.format(version)
  783.     print
  784.  
  785.     # Parse the parameters
  786.  
  787.     parser = argparse.ArgumentParser()
  788.     parser.add_argument(
  789.         '-v',
  790.         '--verbose',
  791.         help='Amount of verbosity. This shows more info about the results.'
  792.             ,
  793.         action='store',
  794.         required=False,
  795.         type=int,
  796.         default=1,
  797.         )
  798.     parser.add_argument(
  799.         '-e',
  800.         '--debug',
  801.         help='Amount of debugging. This shows inner information about the flows.'
  802.             ,
  803.         action='store',
  804.         required=False,
  805.         type=int,
  806.         default=0,
  807.         )
  808.     parser.add_argument('-f', '--file', help='Suricata eve.json file.',
  809.                         action='store', required=False)
  810.     parser.add_argument(
  811.         '-w',
  812.         '--width',
  813.         help='Width of the time window to process. In minutes.',
  814.         action='store',
  815.         required=False,
  816.         type=int,
  817.         default=60,
  818.         )
  819.     parser.add_argument('-d', '--dstnet',
  820.                         help='Destination net to monitor. Ex: 192.168 to search everything attacking 192.168.0.0/16 network'
  821.                         , action='store', required=False)
  822.     parser.add_argument('-p', '--plot',
  823.                         help='Plot the data in an active window.',
  824.                         action='store_true', required=False)
  825.     parser.add_argument(
  826.         '-P',
  827.         '--plotfile',
  828.         help='Store the plot in this file. Extension can be .eps, .png or .pdf. I suggest eps for higher resolution'
  829.             ,
  830.         action='store',
  831.         type=str,
  832.         required=False,
  833.         )
  834.     parser.add_argument('-l', '--log',
  835.                         help='Plot in a logarithmic scale',
  836.                         action='store_true', required=False)
  837.     parser.add_argument(
  838.         '-j',
  839.         '--json',
  840.         help='Json file name to output data in the timewindow in json format. Much less columns outputed.'
  841.             ,
  842.         action='store',
  843.         type=str,
  844.         required=False,
  845.         )
  846.     parser.add_argument('-o', '--ports',
  847.                         help='Compute information about the usage of ports by the attackers. For each combination of ports, count how many unique IPs connected to them. You need also to select JSON output. The results are stored in a file with the same name as the JSON file but with extension .ports'
  848.                         , action='store_true', required=False)
  849.     parser.add_argument('-s', '--summarize_ports',
  850.                         help='Same as -o, but make a summary when the program exits. To have the data in total and not spited by time windows.'
  851.                         , action='store_true', required=False)
  852.     parser.add_argument('-b', '--bandwidth',
  853.                         help='Compute the bandwidth consumption for the given set of ports. Ports numbers should be separated by comma. Only TCP ports supported. Bandwidth is computed in megabits per second on each timewindow, and then it is averaged at the end. The results per time window are included in the json file (so is mandatory to opt it) and the general final values are reported in a file with extension .bandwidth.'
  854.                         , action='store_true', required=False)
  855.     args = parser.parse_args()
  856.  
  857.     # Get the verbosity, if it was not specified as a parameter
  858.  
  859.     if args.verbose < 1:
  860.         args.verbose = 1
  861.  
  862.     # Limit any debuggisity to > 0
  863.  
  864.     if args.debug < 0:
  865.         args.debug = 0
  866.  
  867.     # 1st Get the types of ports and bytes/pkts using IPTablesAnalyzer/iptables_analyzer.py
  868.     # ports_data = subprocess.Popen('./IPTablesAnalyzer/iptables_analyzer.py -j', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()
  869.     # print ports_data
  870.  
  871.     # Prepare Json file and ports file
  872.  
  873.     if args.json:
  874.         jsonfile = open(args.json, 'w')
  875.     if args.ports:
  876.         portsfilename = '.'.join(args.json.split('.')[:-1]) + '.ports'
  877.         portsfile = open(portsfilename, 'w')
  878.  
  879.         # read the interesting dst ports to monitor. This file should be with this distribution.
  880.  
  881.         interesting_ports_file = open('ports.txt')
  882.         interesting_ports = {}
  883.         line = interesting_ports_file.readline().replace('\n', '')
  884.         while line:
  885.             interesting_ports[int(line)] = 1
  886.             line = interesting_ports_file.readline().replace('\n', '')
  887.         interesting_ports_file.close()
  888.  
  889.     current_tw = ''
  890.     try:
  891.         if args.file:
  892.             if args.verbose > 1:
  893.                 print 'Working with the file {} as parameter'.format(args.file)
  894.             f = open(args.file)
  895.             line = f.readline()
  896.             while line:
  897.                 tw = process_line(line)
  898.                 if tw:
  899.                     current_tw = tw
  900.                 line = f.readline()
  901.             f.close()
  902.         else:
  903.             for line in sys.stdin:
  904.                 tw = process_line(line)
  905.                 if tw:
  906.                     current_tw = tw
  907.     except KeyboardInterrupt:
  908.  
  909.         # Do the final things
  910.  
  911.         pass
  912.  
  913.     # # Print last tw
  914.  
  915.     try:
  916.         timestamp = datetime.strptime(current_tw.start_time,
  917.                 timeStampFormat)
  918.         round_down_timestamp = roundTime(timestamp,
  919.                 timedelta(minutes=args.width), 'down')
  920.         str_round_down_timestamp = \
  921.             round_down_timestamp.strftime(timeStampFormat)
  922.         output_tw(str_round_down_timestamp)
  923.     except AttributeError:
  924.         print 'Not a final time window? Some error?'
  925.  
  926.     # Close files
  927.  
  928.     if args.json:
  929.         jsonfile.close()
  930.  
  931.     if args.plot:
  932.         plot()
  933.  
  934.     if args.ports:
  935.  
  936.         # Here we do the final summary of ports in the complete file
  937.  
  938.         if args.summarize_ports:
  939.             summarize_ports()
  940.         portsfile.close()
Add Comment
Please, Sign In to add comment