Commits

György Kohut  committed cd1daa3

add filtering support for queries, stats page for individual binaries

  • Participants
  • Parent commits 7186e39

Comments (0)

Files changed (12)

File hbwebui/decorators.py

 from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest
 from django.template import loader, Context, RequestContext
 
+import re
+
 def login_required_simple(func):
     @wraps(func, assigned=available_attrs(func))
     def inner(request, *args, **kwargs):
 
 def ident_required_simple(func):
     return ident_required(func, simple=True)
+
+def query_filter(func):
+    @wraps(func, assigned=available_attrs(func))
+    def inner(request, *args, **kwargs):
+        #filter_exp = kwargs['filter_exp']
+        #filters_map = {}
+        #if filter_exp:
+        #    try:
+        #        for cond in re.split(r'//*', filter_exp):
+        #            k, v = re.split(r':', cond)
+        #            v = filter(None, set(r.split(r',', v)))
+        #            if v:
+        #                filters_map[k] = v
+        #    except Exception:
+        #            pass
+        #else:
+        #        filters_map = {}
+        return func(request, *args, **kwargs)
+    return inner
+
+def handle_filter(name, r):
+    def decorator(func):
+        @wraps(func, assigned=available_attrs(func))
+        def inner(request, *args, **kwargs):
+            filter_exp = request.GET.get('filter')
+            filters_map = kwargs.get('filters_map', {})
+            if filter_exp:
+                cond_sep = '/'
+                k_sep = ':'
+                v_sep = ','
+                
+                try:
+                    for cond in filter(None, filter_exp.split(cond_sep)):
+                        k, v = cond.split(k_sep, 1)
+                        if k != name:
+                            continue
+                        v = filter(None, v.split(v_sep))
+                        if v:
+                            for s in v:
+                                if not re.match(r, s):
+                                    return HttpResponseBadRequest("invalid value in filter condition '%s': '%s'" % (cond, s))
+                            filters_map[k] = v
+                except ValueError:
+                    return HttpResponseBadRequest("error parsing filter condition: '%s'" % cond)
+            
+            kwargs.update({'filters_map': filters_map})
+            return func(request, *args, **kwargs)
+        return inner
+    return decorator

File hbwebui/queries.py

     tpl = \
         """SELECT
                 md5, sum(agg_main.n_count)::bigint as n_count,
-                count(DISTINCT ident_id) AS n_ident, count(DISTINCT source_ip) AS n_source_ip, count(DISTINCT target_port) AS n_target_port,
+                count(DISTINCT ident_id) AS n_ident, count(DISTINCT source_ip) AS n_source_ip,
+                count(DISTINCT asn) AS n_asn, count(DISTINCT cc) AS n_cc,
+                count(DISTINCT target_port) AS n_target_port,
                 max(agg_main.ts_last) as ts_last, min(agg_main.ts_first) as ts_first
             FROM
                 agg_main
             JOIN
-                dim_ident ON agg_main.ident_id = dim_ident.id
-            JOIN
                 dim_binary ON agg_main.binary_id = dim_binary.id
+            LEFT JOIN
+                att_asn_shadowserver ON agg_main.source_ip = att_asn_shadowserver.ip
             %(where_clause)s
             GROUP BY md5
             %(order_by_clause)s"""
     
     def filter_ident(self, idents):
         self.where_append('filter_ident', 'ident_id IN (SELECT id from dim_ident WHERE ident IN %s)', [tuple(idents)])
+        
+    def filter_md5(self, md5s):
+        self.where_append('filter_md5', 'binary_id IN (SELECT id from dim_binary WHERE md5 IN %s)', [tuple(md5s)])
     
     def filter_source_ip(self, source_ips):
         self.where_append('filter_source_ip', 'source_ip IN %s', [tuple(source_ips)])
     
+    def filter_asn(self, asns):
+        self.where_append('filter_asn', 'asn IN %s', [tuple(asns)])
+    
+    def filter_cc(self, ccs):
+        self.where_append('filter_cc', 'cc IN %s', [tuple(ccs)])
+        
     def order_n_count(self, desc=False):
         self.order_by_append('order_n_count', 'n_count', desc)
 
         JOIN
             agg_main ON t.agg_id = agg_main.agg_id
         JOIN
-            dim_ident ON agg_main.ident_id = dim_ident.id
-        JOIN
             dim_binary ON agg_main.binary_id = dim_binary.id
         GROUP BY md5
         %(order_by_clause)s"""
     tpl = \
         """SELECT
                 count(DISTINCT binary_id) as n_count,
-                count(DISTINCT ident_id) AS n_ident, count(DISTINCT source_ip) AS n_source_ip, count(DISTINCT target_port) AS n_target_port,
+                count(DISTINCT ident_id) AS n_ident, count(DISTINCT source_ip) AS n_source_ip,
+                count(DISTINCT asn) AS n_asn, count(DISTINCT cc) AS n_cc,
+                count(DISTINCT target_port) AS n_target_port,
                 max(agg_main.ts_last) as ts_last, min(agg_main.ts_first) as ts_first,
                 sum(n_count)::bigint as n_total
             FROM
                 agg_main
+            LEFT JOIN
+                att_asn_shadowserver ON agg_main.source_ip = att_asn_shadowserver.ip
             %(where_clause)s"""
     
     def __init__(self):
     db = 'hbstats'
     tpl = \
         """SELECT
-                source_ip, cc, asn,
-                sum(agg_main.n_count)::bigint as n_count,
+                source_ip, sum(agg_main.n_count)::bigint as n_count,
+                cc, asn, as_name,
                 count(DISTINCT ident_id) AS n_ident, count(DISTINCT binary_id) AS n_binary, count(DISTINCT target_port) AS n_target_port,
                 max(agg_main.ts_last) as ts_last, min(agg_main.ts_first) as ts_first
             FROM
                 agg_main
-            JOIN
-                dim_ident ON agg_main.ident_id = dim_ident.id
             LEFT JOIN
                 att_asn_shadowserver ON agg_main.source_ip = att_asn_shadowserver.ip
             %(where_clause)s
-            GROUP BY source_ip, cc, asn
+            GROUP BY source_ip, cc, asn, as_name
             %(order_by_clause)s"""
     
     def __init__(self):
     def filter_ident(self, idents):
         self.where_append('filter_ident', 'ident_id IN (SELECT id from dim_ident WHERE ident IN %s)', [tuple(idents)])
         
+    def filter_md5(self, md5s):
+        self.where_append('filter_md5', 'binary_id IN (SELECT id from dim_binary WHERE md5 IN %s)', [tuple(md5s)])
+    
+    def filter_source_ip(self, source_ips):
+        self.where_append('filter_source_ip', 'source_ip IN %s', [tuple(source_ips)])
+    
+    def filter_asn(self, asns):
+        self.where_append('filter_asn', 'asn IN %s', [tuple(asns)])
+    
+    def filter_cc(self, ccs):
+        self.where_append('filter_cc', 'cc IN %s', [tuple(ccs)])
+        
     def order_n_count(self, desc=False):
         self.order_by_append('order_n_count', 'n_count', desc)
 
                 max(agg_main.ts_last) as ts_last, min(agg_main.ts_first) as ts_first
             FROM
                 agg_main
-            JOIN
-                dim_ident ON agg_main.ident_id = dim_ident.id
             LEFT JOIN
                 att_asn_shadowserver ON agg_main.source_ip = att_asn_shadowserver.ip
             %(where_clause)s
     
     def filter_ident(self, idents):
         self.where_append('filter_ident', 'ident_id IN (SELECT id from dim_ident WHERE ident IN %s)', [tuple(idents)])
+
+    def filter_md5(self, md5s):
+        self.where_append('filter_md5', 'binary_id IN (SELECT id from dim_binary WHERE md5 IN %s)', [tuple(md5s)])
+    
+    def filter_source_ip(self, source_ips):
+        self.where_append('filter_source_ip', 'source_ip IN %s', [tuple(source_ips)])
+    
+    def filter_asn(self, asns):
+        self.where_append('filter_asn', 'asn IN %s', [tuple(asns)])
+    
+    def filter_cc(self, ccs):
+        self.where_append('filter_cc', 'cc IN %s', [tuple(ccs)])
         
     def order_n_count(self, desc=False):
         self.order_by_append('order_n_count', 'n_count', desc)
         """SELECT
                 cc,
                 sum(agg_main.n_count)::bigint as n_count,
-                count(DISTINCT ident_id) AS n_ident, count(DISTINCT binary_id) AS n_binary, count(DISTINCT asn) AS n_asn, count(DISTINCT source_ip) AS n_source_ip, count(DISTINCT target_port) AS n_target_port,
+                count(DISTINCT ident_id) AS n_ident, count(DISTINCT binary_id) AS n_binary, count(DISTINCT source_ip) AS n_source_ip, count(DISTINCT asn) AS n_asn, count(DISTINCT target_port) AS n_target_port,
                 max(agg_main.ts_last) as ts_last, min(agg_main.ts_first) as ts_first
             FROM
                 agg_main
-            JOIN
-                dim_ident ON agg_main.ident_id = dim_ident.id
             LEFT JOIN
                 att_asn_shadowserver ON agg_main.source_ip = att_asn_shadowserver.ip
             %(where_clause)s
     
     def filter_ident(self, idents):
         self.where_append('filter_ident', 'ident_id IN (SELECT id from dim_ident WHERE ident IN %s)', [tuple(idents)])
+    
+    def filter_md5(self, md5s):
+        self.where_append('filter_md5', 'binary_id IN (SELECT id from dim_binary WHERE md5 IN %s)', [tuple(md5s)])
+    
+    def filter_source_ip(self, source_ips):
+        self.where_append('filter_source_ip', 'source_ip IN %s', [tuple(source_ips)])
+    
+    def filter_asn(self, asns):
+        self.where_append('filter_asn', 'asn IN %s', [tuple(asns)])
+    
+    def filter_cc(self, ccs):
+        self.where_append('filter_cc', 'cc IN %s', [tuple(ccs)])
         
     def order_n_count(self, desc=False):
         self.order_by_append('order_n_count', 'n_count', desc)
     
     def filter_ident(self, idents):
         self.where_append('filter_ident', 'ident_id IN (SELECT id from dim_ident WHERE ident IN %s)', [tuple(idents)])
+    
+    def filter_md5(self, md5s):
+        self.where_append('filter_md5', 'binary_id IN (SELECT id from dim_binary WHERE md5 IN %s)', [tuple(md5s)])
+    
+    def filter_source_ip(self, source_ips):
+        self.where_append('filter_source_ip', 'source_ip IN %s', [tuple(source_ips)])
+    
+    def filter_asn(self, asns):
+        self.where_append('filter_asn', 'asn IN %s', [tuple(asns)])
+    
+    def filter_cc(self, ccs):
+        self.where_append('filter_cc', 'cc IN %s', [tuple(ccs)])
+
         
     def order_n_count(self, desc=False):
         self.order_by_append('order_n_count', 'n_count', desc)
     tpl = \
         """SELECT
                 count(DISTINCT ident_id) as n_count,
-                count(DISTINCT binary_id) AS n_binary, count(DISTINCT source_ip) as n_source_ip, count(DISTINCT target_port) AS n_target_port,
-                count(DISTINCT asn) as n_asn, count(DISTINCT cc) as n_cc, 
+                count(DISTINCT binary_id) AS n_binary, count(DISTINCT source_ip) as n_source_ip,
+                count(DISTINCT asn) as n_asn, count(DISTINCT cc) as n_cc,
+                count(DISTINCT target_port) AS n_target_port, 
                 max(agg_main.ts_last) as ts_last, min(agg_main.ts_first) as ts_first,
                 sum(n_count)::bigint as n_total
             FROM
         self.where_init()
     
     def filter_ident(self, idents):
-        self.where_append('filter_ident', 'ident_id IN (SELECT id from dim_ident WHERE ident IN %s)', [tuple(idents)])
+        self.where_append('filter_ident', 'ident_id IN (SELECT id from dim_ident WHERE ident IN %s)', [tuple(idents)])
+
+
+class Stats_Target_Ports(QueryBase):
+    db = 'hbstats'
+    tpl = \
+        """SELECT
+                target_port,
+                sum(agg_main.n_count)::bigint as n_count,
+                count(DISTINCT ident_id) as n_ident, count(DISTINCT binary_id) AS n_binary,
+                count(DISTINCT source_ip) AS n_source_ip, count(DISTINCT asn) as n_asn, count(DISTINCT cc) as n_cc,
+                max(agg_main.ts_last) as ts_last, min(agg_main.ts_first) as ts_first
+            FROM
+                agg_main
+            JOIN
+                dim_ident ON agg_main.ident_id = dim_ident.id
+            LEFT JOIN
+                att_asn_shadowserver ON agg_main.source_ip = att_asn_shadowserver.ip
+            %(where_clause)s
+            GROUP BY target_port
+            %(order_by_clause)s"""
+    
+    def __init__(self):
+        super(Stats_Target_Ports, self).__init__()
+        self.where_init()
+        self.order_by_init()
+    
+    def filter_ident(self, idents):
+        self.where_append('filter_ident', 'ident_id IN (SELECT id from dim_ident WHERE ident IN %s)', [tuple(idents)])
+    
+    def filter_md5(self, md5s):
+        self.where_append('filter_md5', 'binary_id IN (SELECT id from dim_binary WHERE md5 IN %s)', [tuple(md5s)])
+    
+    def filter_source_ip(self, source_ips):
+        self.where_append('filter_source_ip', 'source_ip IN %s', [tuple(source_ips)])
+    
+    def filter_asn(self, asns):
+        self.where_append('filter_asn', 'asn IN %s', [tuple(asns)])
+    
+    def filter_cc(self, ccs):
+        self.where_append('filter_cc', 'cc IN %s', [tuple(ccs)])
+
+        
+    def order_n_count(self, desc=False):
+        self.order_by_append('order_n_count', 'n_count', desc)

File hbwebui/static/js/hbwebui/mod_stats_base.js

 mod_stats_base = (function() {
     
+    var url_stats_binary = 'binary'
+    
+    function buildFilterExp(desc) {
+        var conds = []
+        
+        for (var k in desc) {
+            cond = k + ':' + desc[k].join(',');
+            conds.push(cond)
+        }
+            
+        return '?filter=' + conds.join('/')
+    }
+    
+    
     function base(container, url) {
         this.container = container;
         this.url = url;
     
     
     return {
+        url_stats_binary: url_stats_binary,
+        buildFilterExp: buildFilterExp,
         summary: summary,
         table: table
     };

File hbwebui/static/js/hbwebui/mod_stats_binaries.js

 mod_stats_binaries = (function() {
-    
+        
     function summary(container, url) {
         mod_stats_base.summary.call(this, container, url);
                 
             <h3><%= n_count %> <% print(pluralize(n_ident, "Binary", "Binaries")) %></h3>\
             <ul>\
                 <li>collected by <%= n_ident %> <% print(pluralize(n_ident, "ident")) %></li>\
-                <li>on <%= n_target_port %> target <% print(pluralize(n_target_port, "port")) %></li>\
                 <li>from <%= n_source_ip %> source <% print(pluralize(n_source_ip, "IP")) %></li>\
+                <li>from <%= n_asn %> <% print(pluralize(n_asn, "ASN")) %></li>\
+                <li>from <%= n_cc %> <% print(pluralize(n_cc, "country", "countries")) %></li>\
+                <li>on <%= n_target_port %> target <% print(pluralize(n_target_port, "port")) %></li>\
                 <li>between <%= ts_first %> - <%= ts_last %></li>\
                 <li>in <%= n_total %> <% print(pluralize(n_total, "attack")) %> in total</li>\
             </ul>';
     
     function table(container, url) {
         mod_stats_base.table.call(this, container, url);
-        
+                
         this.dtConfig = $.extend(
             this.dtConfig,
             {
                 'aoColumnDefs': [
-                    { 'sTitle': 'MD5', 'mDataProp': 'md5', 'sType': 'string', 'aTargets': [0], 'fnRender': function(o, val) { return '<a href="#">'+val+'</a>'; }},
+                    { 'sTitle': 'MD5', 'mDataProp': 'md5', 'sType': 'string', 'aTargets': [0], 'fnRender': function(o, val) { return '<a href="'+mod_stats_base.url_stats_binary+'/'+val+'"/>'+val+'</a>'; }},
                     { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [1] },
                     { 'sTitle': '# Idents', 'mDataProp': 'n_ident', 'sType': 'numeric', 'aTargets': [2] },
                     { 'sTitle': '# Source IPs', 'mDataProp': 'n_source_ip', 'sType': 'numeric', 'aTargets': [3] },
-                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [4] },
-                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [5], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
-                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [6], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }}
+                    { 'sTitle': '# ASNs', 'mDataProp': 'n_asn', 'sType': 'numeric', 'aTargets': [4] },
+                    { 'sTitle': '# Countries', 'mDataProp': 'n_cc', 'sType': 'numeric', 'aTargets': [5] },
+                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [6] },
+                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [8], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sDefaultContent': '-', 'aTargets': [0, 1, 2, 3, 4, 5, 6, 7, 8] },
                 ],
                 'aaSorting': [
                     [1, 'desc'],

File hbwebui/static/js/hbwebui/mod_stats_binary.js

+mod_stats_binary = (function() {
+    
+    function table_idents(container, url) {
+        mod_stats_base.table.call(this, container, url);
+        
+        this.dtConfig = $.extend(
+            this.dtConfig,
+            {
+                'aoColumnDefs': [
+                    { 'sTitle': 'Ident', 'mDataProp': 'ident', 'sType': 'string', 'aTargets': [0], 'fnRender': function(o, val) { return '<a href="#">'+val+'</a>'; } },
+                    { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [1] },
+                    { 'sTitle': '# Source IPs', 'mDataProp': 'n_source_ip', 'sType': 'numeric', 'aTargets': [2] },
+                    { 'sTitle': '# ASNs', 'mDataProp': 'n_asn', 'sType': 'numeric', 'aTargets': [3] },
+                    { 'sTitle': '# Countries', 'mDataProp': 'n_cc', 'sType': 'numeric', 'aTargets': [4] },
+                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [5] },
+                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [6], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sDefaultContent': '-', 'aTargets': [0, 1, 2, 3, 4, 5, 6, 7] },
+                ],
+                'aaSorting': [
+                    [1, 'desc']
+                ]
+            }
+        );
+    }
+    table_idents.prototype = utils.extend(mod_stats_base.table);
+    
+    
+    function table_target_ports(container, url) {
+        mod_stats_base.table.call(this, container, url);
+        
+        this.dtConfig = $.extend(
+            this.dtConfig,
+            {
+                'aoColumnDefs': [
+                    { 'sTitle': 'Target Port', 'mDataProp': 'target_port', 'sType': 'numeric', 'aTargets': [0] },
+                    { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [1] },
+                    { 'sTitle': '# Idents', 'mDataProp': 'n_ident', 'sType': 'numeric', 'aTargets': [2] },
+                    { 'sTitle': '# Source IPs', 'mDataProp': 'n_source_ip', 'sType': 'numeric', 'aTargets': [3] },
+                    { 'sTitle': '# ASNs', 'mDataProp': 'n_asn', 'sType': 'numeric', 'aTargets': [4] },
+                    { 'sTitle': '# Countries', 'mDataProp': 'n_cc', 'sType': 'numeric', 'aTargets': [5] },                    
+                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [6], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sDefaultContent': '-', 'aTargets': [0, 1, 2, 3, 4, 5, 6, 7] }
+                ],
+                'aaSorting': [
+                    [1, 'desc'],
+                ]
+            }
+        );
+    }
+    table_target_ports.prototype = utils.extend(mod_stats_base.table);
+    
+    
+    function table_countries(container, url) {
+        mod_stats_base.table.call(this, container, url);
+        
+        this.dtConfig = $.extend(
+            this.dtConfig,
+            {
+                'aoColumnDefs': [
+                    { 'sTitle': 'Country', 'mDataProp': 'cc', 'sType': 'string', 'aTargets': [0], 'fnRender': function(o, val) { val = val.trim(); if (val == '-') return val; return '<a href="#">'+val+'</a>'; } },
+                    { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [1] },
+                    { 'sTitle': '# Idents', 'mDataProp': 'n_ident', 'sType': 'numeric', 'aTargets': [2] },
+                    { 'sTitle': '# Source IPs', 'mDataProp': 'n_source_ip', 'sType': 'numeric', 'aTargets': [3] },
+                    { 'sTitle': '# ASNs', 'mDataProp': 'n_asn', 'sType': 'numeric', 'aTargets': [4] },
+                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [5] },
+                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [6], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sDefaultContent': '-', 'aTargets': [0, 1, 2, 3, 4, 5, 6, 7] },
+                ],
+                'aaSorting': [
+                    [1, 'desc']
+                ]
+            }
+        );
+    }
+    table_countries.prototype = utils.extend(mod_stats_base.table);
+    
+    
+    function table_asns(container, url) {
+        mod_stats_base.table.call(this, container, url);
+        
+        this.dtConfig = $.extend(
+            this.dtConfig,
+            {
+                'aoColumnDefs': [
+                    { 'sTitle': 'AS Name', 'mDataProp': 'as_name', 'sType': 'string', 'aTargets': [0], 'fnRender': function(o, val) { val = val.trim(); if (val == '-') return val; var n = val; if (n.length > 16) n = n.slice(0,16) + '...'; return '<a href="#">'+n+'</a>'; } },
+                    { 'sTitle': 'ASN', 'mDataProp': 'asn', 'sType': 'string', 'aTargets': [1], 'fnRender': function(o, val) { if (val == '-') return val; return '<a href="#">'+val+'</a>'; } },
+                    { 'sTitle': 'Country', 'mDataProp': 'cc', 'sType': 'string', 'aTargets': [2], 'fnRender': function(o, val) { val = val.trim(); if (val == '-') return val; return '<a href="#">'+val+'</a>'; } },
+                    { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [3] },
+                    { 'sTitle': '# Idents', 'mDataProp': 'n_ident', 'sType': 'numeric', 'aTargets': [4] },
+                    { 'sTitle': '# Source IPs', 'mDataProp': 'n_source_ip', 'sType': 'numeric', 'aTargets': [5] },
+                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [6] },
+                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [8], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sDefaultContent': '-', 'aTargets': [0, 1, 2, 3, 4, 5, 6, 7, 8] },
+                ],
+                'aaSorting': [
+                    [3, 'desc']
+                ]
+            }
+        );
+    }
+    table_asns.prototype = utils.extend(mod_stats_base.table);
+    
+    
+    function table_source_ips(container, url) {
+        mod_stats_base.table.call(this, container, url);
+        
+        this.dtConfig = $.extend(
+            this.dtConfig,
+            {
+                'aoColumnDefs': [
+                    { 'sTitle': 'Source IP', 'mDataProp': 'source_ip', 'sType': 'string', 'aTargets': [0], 'fnRender': function(o, val) { return '<a href="#">'+val+'</a>'; }},
+                    { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [1] },
+                    { 'sTitle': 'Country', 'mDataProp': 'cc', 'sType': 'string', 'aTargets': [2], 'fnRender': function(o, val) { val = val.trim(); if (val == '-') return val; return '<a href="#">'+val+'</a>'; } },
+                    { 'sTitle': 'AS Name', 'mDataProp': 'as_name', 'sType': 'string', 'aTargets': [3], 'fnRender': function(o, val) { val = val.trim(); if (val == '-') return val; var n = val; if (n.length > 16) n = n.slice(0,16) + '...'; return '<a href="#">'+n+'</a>'; } },
+                    { 'sTitle': 'ASN', 'mDataProp': 'asn', 'sType': 'string', 'aTargets': [4], 'fnRender': function(o, val) { if (val == '-') return val; return '<a href="#">'+val+'</a>'; } },
+                    { 'sTitle': '# Idents', 'mDataProp': 'n_ident', 'sType': 'numeric', 'aTargets': [5] },
+                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [6] },
+                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [8], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sDefaultContent': '-', 'aTargets': [0, 1, 2, 3, 4, 5, 6, 7, 8] }
+                ],
+                'aaSorting': [
+                    [1, 'desc'],
+                ]
+            }
+        );
+    }
+    table_source_ips.prototype = utils.extend(mod_stats_base.table);
+        
+    
+    return {
+        summary: null,
+        table_idents: table_idents,
+        table_target_ports: table_target_ports,
+        table_countries: table_countries,
+        table_asns: table_asns,
+        table_source_ips: table_source_ips
+    };
+})();

File hbwebui/static/js/hbwebui/mod_stats_countries.js

                     { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [1] },
                     { 'sTitle': '# Idents', 'mDataProp': 'n_ident', 'sType': 'numeric', 'aTargets': [2] },
                     { 'sTitle': '# Binaries', 'mDataProp': 'n_binary', 'sType': 'numeric', 'aTargets': [3] },
-                    { 'sTitle': '# ASNs', 'mDataProp': 'n_asn', 'sType': 'numeric', 'aTargets': [4] },
-                    { 'sTitle': '# Source IPs', 'mDataProp': 'n_source_ip', 'sType': 'numeric', 'aTargets': [5] },
+                    { 'sTitle': '# Source IPs', 'mDataProp': 'n_source_ip', 'sType': 'numeric', 'aTargets': [4] },
+                    { 'sTitle': '# ASNs', 'mDataProp': 'n_asn', 'sType': 'numeric', 'aTargets': [5] },
                     { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [6] },
                     { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
                     { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [8], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},

File hbwebui/static/js/hbwebui/mod_stats_idents.js

                     { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [1] },
                     { 'sTitle': '# Binaries', 'mDataProp': 'n_binary', 'sType': 'numeric', 'aTargets': [2] },
                     { 'sTitle': '# Source IPs', 'mDataProp': 'n_source_ip', 'sType': 'numeric', 'aTargets': [3] },
-                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [4] },
-                    { 'sTitle': '# ASNs', 'mDataProp': 'n_asn', 'sType': 'numeric', 'aTargets': [5] },
-                    { 'sTitle': '# Countries', 'mDataProp': 'n_cc', 'sType': 'numeric', 'aTargets': [6] },
+                    { 'sTitle': '# ASNs', 'mDataProp': 'n_asn', 'sType': 'numeric', 'aTargets': [4] },
+                    { 'sTitle': '# Countries', 'mDataProp': 'n_cc', 'sType': 'numeric', 'aTargets': [5] },
+                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [6] },
                     { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
                     { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [8], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
                     { 'sDefaultContent': '-', 'aTargets': [0, 1, 2, 3, 4, 5, 6, 7, 8] },

File hbwebui/static/js/hbwebui/mod_stats_source_ips.js

             {
                 'aoColumnDefs': [
                     { 'sTitle': 'Source IP', 'mDataProp': 'source_ip', 'sType': 'string', 'aTargets': [0], 'fnRender': function(o, val) { return '<a href="#">'+val+'</a>'; }},
-                    { 'sTitle': 'Country', 'mDataProp': 'cc', 'sType': 'string', 'aTargets': [1], },
-                    { 'sTitle': 'ASN', 'mDataProp': 'asn', 'sType': 'numeric', 'aTargets': [2] },
-                    { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [3] },
-                    { 'sTitle': '# Idents', 'mDataProp': 'n_ident', 'sType': 'numeric', 'aTargets': [4] },
-                    { 'sTitle': '# Binaries', 'mDataProp': 'n_binary', 'sType': 'numeric', 'aTargets': [5] },
-                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [6] },
-                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [7], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
-                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [8], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }}
+                    { 'sTitle': '# Attacks', 'mDataProp': 'n_count', 'sType': 'numeric', 'aTargets': [1] },
+                    { 'sTitle': 'Country', 'mDataProp': 'cc', 'sType': 'string', 'aTargets': [2], 'fnRender': function(o, val) { val = val.trim(); if (val == '-') return val; return '<a href="#">'+val+'</a>'; } },
+                    { 'sTitle': 'AS Name', 'mDataProp': 'as_name', 'sType': 'string', 'aTargets': [3], 'fnRender': function(o, val) { val = val.trim(); if (val == '-') return val; var n = val; if (n.length > 16) n = n.slice(0,16) + '...'; return '<a href="#">'+n+'</a>'; } },
+                    { 'sTitle': 'ASN', 'mDataProp': 'asn', 'sType': 'string', 'aTargets': [4], 'fnRender': function(o, val) { if (val == '-') return val; return '<a href="#">'+val+'</a>'; } },
+                    { 'sTitle': '# Idents', 'mDataProp': 'n_ident', 'sType': 'numeric', 'aTargets': [5] },
+                    { 'sTitle': '# Binaries', 'mDataProp': 'n_binary', 'sType': 'numeric', 'aTargets': [6] },
+                    { 'sTitle': '# Target Ports', 'mDataProp': 'n_target_port', 'sType': 'numeric', 'aTargets': [7] },
+                    { 'sTitle': 'Last', 'mDataProp': 'ts_last', 'sType': null, 'aTargets': [8], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sTitle': 'First', 'mDataProp': 'ts_first', 'sType': null, 'aTargets': [9], 'fnRender': function(o, val) { return mod_base.renderTimestamp(val); }},
+                    { 'sDefaultContent': '-', 'aTargets': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] }
                 ],
                 'aaSorting': [
-                    [3, 'desc'],
+                    [1, 'desc'],
                 ]
             }
         );

File hbwebui/templates/navbar.html

                         {% endif %}
                     </ul>
                 </li>
+                {% if page == "stats_binary" %}
+                <li class="active"><a href="#">Stats: binary: {{ md5 }}</a></li>
+                {% endif %}
             </ul>
             <div class="nav pull-right dropdown">
                 <a class="btn dropdown-toggle" data-toggle="dropdown" href="#"><span class="icon-user"></span> {{ user.username }} <span class="caret"></span></a>

File hbwebui/templates/stats_binary.html

+{% extends "bootstrap.html" %}
+{% block bootstrap-head %}
+
+{% load staticfiles %}
+
+<script src="{% static "js/lodash.min.js" %}"></script>
+<script src="{% static "js/hbwebui/utils.js" %}"></script>
+<script src="{% static "js/hbwebui/mod_base.js" %}"></script>
+<script src="{% static "js/hbwebui/mod_stats_base.js" %}"></script>
+<script src="{% static "js/hbwebui/mod_stats_binary.js" %}"></script>
+
+
+<title>Stats for binary: {{ md5 }}</title>
+
+
+<script>
+    md5 = '{{ md5 }}';
+    urls = {
+        'summary': '',
+        'table_idents': '{% url r_stats_idents %}',
+        'table_countries': '{% url r_stats_countries %}',
+        'table_asns': '{% url r_stats_asns %}',
+        'table_source_ips': '{% url r_stats_source_ips %}',
+        'table_target_ports': '{% url r_stats_target_ports %}',
+    }    
+    
+    $(
+        function() {
+            filterExp = mod_stats_base.buildFilterExp({'md5': [md5]});
+            console.log(md5);
+            
+            mod_table_idents = new mod_stats_binary.table_idents($('#mod-table-idents'), urls.table_idents + filterExp);
+            mod_table_idents.render();
+            
+            mod_table_target_ports = new mod_stats_binary.table_target_ports($('#mod-table-target_ports'), urls.table_target_ports + filterExp);
+            mod_table_target_ports.render();
+            
+            mod_table_countries = new mod_stats_binary.table_countries($('#mod-table-countries'), urls.table_countries + filterExp);
+            mod_table_countries.render();
+            
+            mod_table_asns = new mod_stats_binary.table_asns($('#mod-table-asns'), urls.table_asns + filterExp);
+            mod_table_asns.render();
+            
+            mod_table_source_ips = new mod_stats_binary.table_source_ips($('#mod-table-source_ips'), urls.table_source_ips + filterExp);
+            mod_table_source_ips.render();            
+        }
+    );
+</script>
+
+{% endblock %}
+
+{% block bootstrap-body %}
+{% include "navbar.html" %}
+
+<div class="container-fluid">
+    <h3>Stats for binary {{ md5 }}</h3>
+    <hr/>
+    <div class="row-fluid">
+        <h3>By Ident</h3>
+        <div id="mod-table-idents"></div>
+    </div>
+    <hr/>
+    <div class="row-fluid">
+        <h3>By Target Port</h3>
+        <div id="mod-table-target_ports"></div>
+    </div>
+    <hr/>
+    <div class="row-fluid">
+        <h3>By Country</h3>
+        <div id="mod-table-countries"></div>
+    </div>
+    <hr/>
+    <div class="row-fluid">
+        <h3>By ASN</h3>
+        <div id="mod-table-asns"></div>
+    </div>
+    <hr/>
+    <div class="row-fluid">
+        <h3>By Source IP</h3>
+        <div id="mod-table-source_ips"></div>
+    </div>
+</div>
+
+{% endblock %}

File hbwebui/urls.py

     url(r'^stats/countries$', 'stats_countries', name='stats_countries'),
     url(r'^stats/idents$', 'stats_idents', name='stats_idents'),
     
+    url(r'^stats/binary/([0-9a-z]{32})', 'stats_binary', name='stats_binary'),
+    
     url(r'^r/stats/binaries$', 'r_stats_binaries', name='r_stats_binaries'),
     url(r'^r/stats/binaries/summary$', 'r_stats_binaries_summary', name='r_stats_binaries_summary'),
     
     url(r'^r/stats/idents$', 'r_stats_idents', name='r_stats_idents'),
     url(r'^r/stats/idents/summary$', 'r_stats_idents_summary', name='r_stats_idents_summary'),
     
-    
+    url(r'^r/stats/target_ports$', 'r_stats_target_ports', name='r_stats_target_ports'),
 
     
     url(r'^r/myidents', 'myidents', name='myidents'),

File hbwebui/views.py

 from django.contrib import auth
 from models import Ident, IdentToGroupMapping
 
-from decorators import login_required_simple, ident_required, ident_required_simple
+from decorators import login_required_simple, ident_required, ident_required_simple, handle_filter
 import util
 
 import queries
 
 login_url = '/hbwebui/login'
+filter_ident = ('ident', r'^[0-9A-Za-z]{5}@[0-9A-Za-z]{3}$')
+filter_md5 = ('md5', r'^[0-9a-z]{32}$')
+filter_source_ip = ('source_ip', r'^[0-9A-Za-z]{5}@[0-9A-Za-z]{3}$')
+filter_asn = ('asn', r'^[0-9]+$')
+filter_cc = ('cc', r'^[A-Z]{2}$')
 
 @login_required(login_url=login_url)
 @ident_required
 
 @login_required_simple
 @ident_required_simple
-def r_stats_binaries(request):
+@handle_filter(*filter_ident)
+@handle_filter(*filter_md5)
+@handle_filter(*filter_source_ip)
+@handle_filter(*filter_asn)
+@handle_filter(*filter_cc)
+def r_stats_binaries(request, filters_map):
     idents = request.session['idents']
     
     q = queries.Stats_Binaries()
-    q.filter_ident(idents)
+    if filters_map.get('ident'):
+        q.filter_ident(filter(lambda i: i in idents, filters_map.pop('ident')))
+    else:
+        q.filter_ident(idents)
+    for k in filters_map:
+        getattr(q, 'filter_' + k)(filters_map[k])
     q.order_n_count(desc=True)
     cursor = q.execute()
     qr = util.dictfetchall(cursor)
     
+    print q.build()
+    
     r = []
     for d in qr:
         o = {}
         o['n_count'] = d['n_count']
         o['n_ident'] = d['n_ident']
         o['n_source_ip'] = d['n_source_ip']
+        o['n_asn'] = d['n_asn']
+        o['n_cc'] = d['n_cc']
         o['n_target_port'] = d['n_target_port']
         o['ts_last'] = calendar.timegm(d['ts_last'].utctimetuple())
         o['ts_first'] = calendar.timegm(d['ts_first'].utctimetuple())
     r['n_count'] = qr[0]['n_count']
     r['n_ident'] = qr[0]['n_ident']
     r['n_source_ip'] = qr[0]['n_source_ip']
+    r['n_asn'] = qr[0]['n_asn']
+    r['n_cc'] = qr[0]['n_cc']
     r['n_target_port'] = qr[0]['n_target_port']
     r['ts_last'] = calendar.timegm(qr[0]['ts_last'].utctimetuple())
     r['ts_first'] = calendar.timegm(qr[0]['ts_first'].utctimetuple())
 
 @login_required_simple
 @ident_required_simple
-def r_stats_source_ips(request):
+@handle_filter(*filter_ident)
+@handle_filter(*filter_md5)
+@handle_filter(*filter_source_ip)
+@handle_filter(*filter_asn)
+@handle_filter(*filter_cc)
+def r_stats_source_ips(request, filters_map):
     idents = request.session['idents']
     
     q = queries.Stats_Source_IPs()
-    q.filter_ident(idents)
+    if filters_map.get('ident'):
+        q.filter_ident(filter(lambda i: i in idents, filters_map.pop('ident')))
+    else:
+        q.filter_ident(idents)
+    for k in filters_map:
+        getattr(q, 'filter_' + k)(filters_map[k])
     q.order_n_count(desc=True)
     cursor = q.execute()
     qr = util.dictfetchall(cursor)
     for d in qr:
         o = {}
         o['source_ip'] = d['source_ip']
+        o['n_count'] = d['n_count']
         o['cc'] = d['cc']
         o['asn'] = d['asn']
-        o['n_count'] = d['n_count']
+        o['as_name'] = d['as_name']
         o['n_ident'] = d['n_ident']
         o['n_binary'] = d['n_binary']
         o['n_target_port'] = d['n_target_port']
 
 @login_required_simple
 @ident_required_simple
-def r_stats_asns(request):
+@handle_filter(*filter_ident)
+@handle_filter(*filter_md5)
+@handle_filter(*filter_source_ip)
+@handle_filter(*filter_asn)
+@handle_filter(*filter_cc)
+def r_stats_asns(request, filters_map):
     idents = request.session['idents']
     
     q = queries.Stats_ASNs()
-    q.filter_ident(idents)
+    if filters_map.get('ident'):
+        q.filter_ident(filter(lambda i: i in idents, filters_map.pop('ident')))
+    else:
+        q.filter_ident(idents)
+    for k in filters_map:
+        getattr(q, 'filter_' + k)(filters_map[k])
     q.order_n_count(desc=True)
     cursor = q.execute()
     qr = util.dictfetchall(cursor)
-    
+        
     r = []
     for d in qr:
         o = {}
 
 @login_required_simple
 @ident_required_simple
-def r_stats_countries(request):
+@handle_filter(*filter_ident)
+@handle_filter(*filter_md5)
+@handle_filter(*filter_source_ip)
+@handle_filter(*filter_asn)
+@handle_filter(*filter_cc)
+def r_stats_countries(request, filters_map):
     idents = request.session['idents']
     
     q = queries.Stats_Countries()
-    q.filter_ident(idents)
+    if filters_map.get('ident'):
+        q.filter_ident(filter(lambda i: i in idents, filters_map.pop('ident')))
+    else:
+        q.filter_ident(idents)
+    for k in filters_map:
+        getattr(q, 'filter_' + k)(filters_map[k])
     q.order_n_count(desc=True)
     cursor = q.execute()
     qr = util.dictfetchall(cursor)
         o['n_count'] = d['n_count']
         o['n_ident'] = d['n_ident']
         o['n_binary'] = d['n_binary']
-        o['n_asn'] = d['n_asn']
         o['n_source_ip'] = d['n_source_ip']
+        o['n_asn'] = d['n_asn']
         o['n_target_port'] = d['n_target_port']
         o['ts_last'] = calendar.timegm(d['ts_last'].utctimetuple())
         o['ts_first'] = calendar.timegm(d['ts_first'].utctimetuple())
 
 @login_required_simple
 @ident_required_simple
-def r_stats_idents(request):
+@handle_filter(*filter_ident)
+@handle_filter(*filter_md5)
+@handle_filter(*filter_source_ip)
+@handle_filter(*filter_asn)
+@handle_filter(*filter_cc)
+def r_stats_idents(request, filters_map):
     idents = request.session['idents']
     
     q = queries.Stats_Idents()
-    q.filter_ident(idents)
+    if filters_map.get('ident'):
+        q.filter_ident(filter(lambda i: i in idents, filters_map.pop('ident')))
+    else:
+        q.filter_ident(idents)
+    for k in filters_map:
+        getattr(q, 'filter_' + k)(filters_map[k])
     q.order_n_count(desc=True)
     cursor = q.execute()
     qr = util.dictfetchall(cursor)
         o['n_count'] = d['n_count']
         o['n_binary'] = d['n_binary']
         o['n_source_ip'] = d['n_source_ip']
-        o['n_target_port'] = d['n_target_port']
         o['n_asn'] = d['n_asn']
         o['n_cc'] = d['n_cc']
+        o['n_target_port'] = d['n_target_port']
         o['ts_last'] = calendar.timegm(d['ts_last'].utctimetuple())
         o['ts_first'] = calendar.timegm(d['ts_first'].utctimetuple())
         r.append(o)
     
     return response
 
+@login_required_simple
+@ident_required_simple
+@handle_filter(*filter_ident)
+@handle_filter(*filter_md5)
+@handle_filter(*filter_source_ip)
+@handle_filter(*filter_asn)
+@handle_filter(*filter_cc)
+def r_stats_target_ports(request, filters_map):
+    idents = request.session['idents']
+    
+    q = queries.Stats_Target_Ports()
+    if filters_map.get('ident'):
+        q.filter_ident(filter(lambda i: i in idents, filters_map.pop('ident')))
+    else:
+        q.filter_ident(idents)
+    for k in filters_map:
+        getattr(q, 'filter_' + k)(filters_map[k])
+    q.order_n_count(desc=True)
+    cursor = q.execute()
+    qr = util.dictfetchall(cursor)
+    
+    r = []
+    for d in qr:
+        o = {}
+        o['target_port'] = d['target_port']
+        o['n_count'] = d['n_count']
+        o['n_ident'] = d['n_ident']
+        o['n_binary'] = d['n_binary']
+        o['n_source_ip'] = d['n_source_ip']
+        o['n_asn'] = d['n_asn']
+        o['n_cc'] = d['n_cc']
+        o['ts_last'] = calendar.timegm(d['ts_last'].utctimetuple())
+        o['ts_first'] = calendar.timegm(d['ts_first'].utctimetuple())
+        r.append(o)
+    
+    response = HttpResponse(mimetype='application/json')
+    json.dump(r, response)
+    
+    return response
+
+@login_required
+@ident_required
+def stats_binary(request, md5):
+    idents = request.session['idents']
+        
+    t = loader.get_template('stats_binary.html')
+    c = RequestContext(request, {
+            'page': 'stats_binary',
+            'md5': md5,
+            'idents': json.dumps(idents),
+    })
+    return HttpResponse(t.render(c))
+
 # example
 @login_required_simple
 @ident_required_simple