by2 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +50 -0
- data/Rakefile +98 -0
- data/bin/by2 +16 -0
- data/by2.gemspec +32 -0
- data/config/database.yml.example +11 -0
- data/config/setup.sql +9 -0
- data/db/migrate/20140205014806_init_db.rb +147 -0
- data/db/schema.rb +160 -0
- data/lib/by2.rb +69 -0
- data/lib/by2/client.rb +109 -0
- data/lib/by2/config_loader.rb +34 -0
- data/lib/by2/ext/active_record.rb +105 -0
- data/lib/by2/models.rb +10 -0
- data/lib/by2/models/event.rb +50 -0
- data/lib/by2/models/icmphdr.rb +10 -0
- data/lib/by2/models/iphdr.rb +38 -0
- data/lib/by2/models/payload.rb +16 -0
- data/lib/by2/models/tcphdr.rb +30 -0
- data/lib/by2/models/udphdr.rb +30 -0
- data/lib/by2/options.rb +77 -0
- data/lib/by2/utils.rb +22 -0
- data/lib/by2/version.rb +3 -0
- data/man/by2.1 +105 -0
- data/man/by2.1.ronn +98 -0
- data/man/by2.1.txt +107 -0
- data/spec/by2/client_spec.rb +157 -0
- data/spec/by2/models/event_spec.rb +14 -0
- data/spec/by2/options_spec.rb +107 -0
- data/spec/by2/utils_spec.rb +19 -0
- data/spec/fixtures/data.yml +19 -0
- data/spec/fixtures/event.yml +36 -0
- data/spec/fixtures/icmphdr.yml +7 -0
- data/spec/fixtures/iphdr.yml +108 -0
- data/spec/fixtures/tcphdr.yml +55 -0
- data/spec/fixtures/udphdr.yml +7 -0
- data/spec/spec_helper.rb +55 -0
- metadata +235 -0
data/lib/by2.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'yaml'
|
3
|
+
require "optparse"
|
4
|
+
require 'ostruct'
|
5
|
+
require 'pg'
|
6
|
+
require 'active_record'
|
7
|
+
require 'composite_primary_keys'
|
8
|
+
|
9
|
+
require_relative 'by2/version'
|
10
|
+
require_relative 'by2/options'
|
11
|
+
require_relative 'by2/config_loader'
|
12
|
+
require_relative 'by2/utils'
|
13
|
+
require_relative 'by2/ext/active_record'
|
14
|
+
require_relative 'by2/models'
|
15
|
+
require_relative 'by2/client'
|
16
|
+
|
17
|
+
|
18
|
+
module By2
|
19
|
+
def self.root
|
20
|
+
@root ||= File.expand_path(File.join(__FILE__, '..', '..'))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.config
|
24
|
+
@config ||= ConfigLoader.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.fixtures_dir
|
28
|
+
File.join(root, "spec", "fixtures")
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.db_connect
|
32
|
+
@conn ||= begin
|
33
|
+
conn_config = config.load("database.yml")
|
34
|
+
#ActiveSupport::Deprecation.silenced = true
|
35
|
+
ActiveRecord::Base.default_timezone = :local
|
36
|
+
ActiveRecord::Base.establish_connection(conn_config[env])
|
37
|
+
ActiveRecord::Base.connection
|
38
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT) if debug?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.env
|
43
|
+
@env ||= (ENV['BY2_ENV'] || config.load("env.yml", false) || "development")
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.debug?
|
47
|
+
@debug ||= false
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.debug=(d)
|
51
|
+
@debug = d
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.debug(msg)
|
55
|
+
$stdout.puts(msg) if debug?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
By2.db_connect
|
61
|
+
|
62
|
+
=begin
|
63
|
+
require "./lib/by2"
|
64
|
+
By2.db_connect
|
65
|
+
cli = By2::Client.new([])
|
66
|
+
ip = By2::Utils.int32_to_ip(2154959208)
|
67
|
+
|
68
|
+
q = cli.find_entries({ip: ip});
|
69
|
+
=end
|
data/lib/by2/client.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
module By2
|
2
|
+
class Client
|
3
|
+
include Models
|
4
|
+
|
5
|
+
def initialize(argv=[])
|
6
|
+
@opts = Options.parse(argv)
|
7
|
+
|
8
|
+
By2.debug = @opts.delete(:debug)
|
9
|
+
By2.db_connect
|
10
|
+
By2.debug(@opts.inspect)
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# payload: sid: 1, cid: 5
|
15
|
+
# 2149599422 => "128.32.72.190"
|
16
|
+
# 2954912804 => "176.32.100.36"
|
17
|
+
def find_records(options = {})
|
18
|
+
@opts = @opts.merge(options)
|
19
|
+
tables = %w(iphdr tcphdr udphdr icmphdr payload)
|
20
|
+
|
21
|
+
query = Event.
|
22
|
+
includes(*tables).
|
23
|
+
references(*tables).
|
24
|
+
order("event.timestamp")
|
25
|
+
|
26
|
+
query.
|
27
|
+
merge(ip_src_or_dst).
|
28
|
+
merge(port_src_or_dst).
|
29
|
+
merge(port_src).
|
30
|
+
merge(port_dst).
|
31
|
+
merge(ip_src).
|
32
|
+
merge(ip_dst).
|
33
|
+
merge(date).
|
34
|
+
merge(date_range)
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
records = find_records
|
39
|
+
|
40
|
+
unless @opts[:count]
|
41
|
+
records.each { |r| $stdout.puts(terminal(r)) }
|
42
|
+
end
|
43
|
+
|
44
|
+
$stdout.puts(record_separator)
|
45
|
+
$stdout.puts(record_count(records.count))
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def record_separator
|
51
|
+
("-" * 80)
|
52
|
+
end
|
53
|
+
|
54
|
+
def record_count(n)
|
55
|
+
"Total records: #{n}\n\n"
|
56
|
+
end
|
57
|
+
|
58
|
+
def terminal(event)
|
59
|
+
e = event
|
60
|
+
("-" * 80) + "\n" \
|
61
|
+
"[#{e.timestamp}] #{e.iphdr.ipaddr_src}:#{e.sport} -> #{e.iphdr.ipaddr_dst}:#{e.dport} (#{e.transport})\n\n" \
|
62
|
+
"#{(e.payload && e.payload.to_s.strip) || "[no payload]"}\n\n"
|
63
|
+
end
|
64
|
+
|
65
|
+
def all
|
66
|
+
Event.all
|
67
|
+
end
|
68
|
+
|
69
|
+
def ip_src_or_dst
|
70
|
+
return all if @opts[:ip].nil?
|
71
|
+
Iphdr.ip_src_or_dst(@opts[:ip])
|
72
|
+
end
|
73
|
+
|
74
|
+
def port_src_or_dst
|
75
|
+
return all if @opts[:port].nil?
|
76
|
+
Tcphdr.src_or_dst_port(@opts[:port]).or(Udphdr.src_or_dst_port(@opts[:port]))
|
77
|
+
end
|
78
|
+
|
79
|
+
def port_src
|
80
|
+
return all if @opts[:src_port].nil?
|
81
|
+
Tcphdr.src_port(@opts[:src_port]).or(Udphdr.src_port(@opts[:src_port]))
|
82
|
+
end
|
83
|
+
|
84
|
+
def port_dst
|
85
|
+
return all if @opts[:dst_port].nil?
|
86
|
+
Tcphdr.dst_port(@opts[:dst_port]).or(Udphdr.dst_port(@opts[:dst_port]))
|
87
|
+
end
|
88
|
+
|
89
|
+
def ip_src
|
90
|
+
return all if @opts[:src_ip].nil?
|
91
|
+
Iphdr.ip_src(@opts[:src_ip])
|
92
|
+
end
|
93
|
+
|
94
|
+
def ip_dst
|
95
|
+
return all if @opts[:dst_ip].nil?
|
96
|
+
Iphdr.ip_dst(@opts[:dst_ip])
|
97
|
+
end
|
98
|
+
|
99
|
+
def date
|
100
|
+
return all if @opts[:date].nil?
|
101
|
+
Event.on_date(@opts[:date])
|
102
|
+
end
|
103
|
+
|
104
|
+
def date_range
|
105
|
+
return all if @opts[:start_date].nil?
|
106
|
+
Event.in_date_range(@opts[:start_date], @opts[:end_date])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module By2
|
2
|
+
class ConfigLoader
|
3
|
+
attr_accessor :search_paths
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@search_paths ||= [app_config, user_config]
|
7
|
+
end
|
8
|
+
|
9
|
+
def load(config_file, required = false)
|
10
|
+
search_paths.each do |path|
|
11
|
+
file = File.expand_path(File.join(path, config_file))
|
12
|
+
By2.debug("Searching for #{file} in #{path}")
|
13
|
+
|
14
|
+
if File.exists?(file)
|
15
|
+
By2.debug("Loaded: #{file}")
|
16
|
+
return YAML.load_file(file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
By2.debug("Could not load: #{config_file}")
|
21
|
+
required ? raise : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def app_config
|
27
|
+
@app_config ||= File.expand_path(File.join(::By2.root, "config"))
|
28
|
+
end
|
29
|
+
|
30
|
+
def user_config
|
31
|
+
@user_config ||= File.expand_path(File.join(ENV['HOME'], ".by2"))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# At some point this should be merged into ActiveRecord or a gem should
|
2
|
+
# become available with this same functionality.
|
3
|
+
#
|
4
|
+
# Original gist: https://gist.github.com/j-mcnally/250eaaceef234dd8971b
|
5
|
+
# Github Pull Request: https://github.com/rails/rails/pull/9052
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module Querying
|
9
|
+
delegate :or, :to => :all
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ActiveRecord
|
14
|
+
module QueryMethods
|
15
|
+
# OrChain objects act as placeholder for queries in which #or does not have any parameter.
|
16
|
+
# In this case, #or must be chained with any other relation method to return a new relation.
|
17
|
+
# It is intended to allow .or.where() and .or.named_scope.
|
18
|
+
class OrChain
|
19
|
+
def initialize(scope)
|
20
|
+
@scope = scope
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method, *args, &block)
|
24
|
+
right_relation = @scope.klass.unscoped do
|
25
|
+
@scope.klass.send(method, *args, &block)
|
26
|
+
end
|
27
|
+
@scope.or(right_relation)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a new relation, which is the result of filtering the current relation
|
32
|
+
# according to the conditions in the arguments, joining WHERE clauses with OR
|
33
|
+
# operand, contrary to the default behaviour that uses AND.
|
34
|
+
#
|
35
|
+
# #or accepts conditions in one of several formats. In the examples below, the resulting
|
36
|
+
# SQL is given as an illustration; the actual query generated may be different depending
|
37
|
+
# on the database adapter.
|
38
|
+
#
|
39
|
+
# === without arguments
|
40
|
+
#
|
41
|
+
# If #or is used without arguments, it returns an ActiveRecord::OrChain object that can
|
42
|
+
# be used to chain queries with any other relation method, like where:
|
43
|
+
#
|
44
|
+
# Post.where("id = 1").or.where("id = 2")
|
45
|
+
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
|
46
|
+
#
|
47
|
+
# It can also be chained with a named scope:
|
48
|
+
#
|
49
|
+
# Post.where("id = 1").or.containing_the_letter_a
|
50
|
+
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'body LIKE \\'%a%\\''))
|
51
|
+
#
|
52
|
+
# === ActiveRecord::Relation
|
53
|
+
#
|
54
|
+
# When #or is used with an ActiveRecord::Relation as an argument, it merges the two
|
55
|
+
# relations, with the exception of the WHERE clauses, that are joined using the OR
|
56
|
+
# operand.
|
57
|
+
#
|
58
|
+
# Post.where("id = 1").or(Post.where("id = 2"))
|
59
|
+
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
|
60
|
+
#
|
61
|
+
# === anything you would pass to #where
|
62
|
+
#
|
63
|
+
# #or also accepts anything that could be passed to the #where method, as
|
64
|
+
# a shortcut:
|
65
|
+
#
|
66
|
+
# Post.where("id = 1").or("id = ?", 2)
|
67
|
+
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
|
68
|
+
#
|
69
|
+
def or(opts = :chain, *rest)
|
70
|
+
if opts == :chain
|
71
|
+
OrChain.new(self)
|
72
|
+
else
|
73
|
+
left = with_default_scope
|
74
|
+
right = (ActiveRecord::Relation === opts) ? opts : klass.unscoped.where(opts, rest)
|
75
|
+
|
76
|
+
unless left.where_values.empty? || right.where_values.empty?
|
77
|
+
left.where_values = [left.where_ast.or(right.where_ast)]
|
78
|
+
right.where_values = []
|
79
|
+
end
|
80
|
+
|
81
|
+
left = left.merge(right)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Returns an Arel AST containing only where_values
|
87
|
+
def where_ast
|
88
|
+
arel_wheres = []
|
89
|
+
|
90
|
+
where_values.each do |where|
|
91
|
+
arel_wheres << (String === where ? Arel.sql(where) : where)
|
92
|
+
end
|
93
|
+
|
94
|
+
return Arel::Nodes::And.new(arel_wheres) if arel_wheres.length >= 2
|
95
|
+
|
96
|
+
if Arel::Nodes::SqlLiteral === arel_wheres.first
|
97
|
+
Arel::Nodes::Grouping.new(arel_wheres.first)
|
98
|
+
else
|
99
|
+
arel_wheres.first
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
data/lib/by2/models.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module By2
|
2
|
+
module Models
|
3
|
+
|
4
|
+
class Event < ActiveRecord::Base
|
5
|
+
self.table_name = 'event'
|
6
|
+
self.primary_keys = :sid, :cid
|
7
|
+
|
8
|
+
has_one :payload, :foreign_key => [:sid, :cid]
|
9
|
+
has_one :tcphdr, :foreign_key => [:sid, :cid]
|
10
|
+
has_one :udphdr, :foreign_key => [:sid, :cid]
|
11
|
+
has_one :icmphdr, :foreign_key => [:sid, :cid]
|
12
|
+
has_one :iphdr, :foreign_key => [:sid, :cid]
|
13
|
+
|
14
|
+
|
15
|
+
def self.on_date(date)
|
16
|
+
where("cast(\"timestamp\" as date) = ?", date)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.in_date_range(start_date, end_date)
|
20
|
+
query = where("cast(\"timestamp\" as date) >= ?", start_date)
|
21
|
+
query = query.merge(where("cast(\"timestamp\" as date) <= ?", end_date)) if end_date
|
22
|
+
query
|
23
|
+
end
|
24
|
+
|
25
|
+
def transport
|
26
|
+
return "TCP" if tcphdr
|
27
|
+
return "UCP" if udphdr
|
28
|
+
return "ICMP" if icmphdr
|
29
|
+
|
30
|
+
"UNKNOWN"
|
31
|
+
end
|
32
|
+
|
33
|
+
def dport
|
34
|
+
tcphdr.try(:dport) || udphdr.try(:dport)
|
35
|
+
end
|
36
|
+
|
37
|
+
def sport
|
38
|
+
tcphdr.try(:sport) || udphdr.try(:sport)
|
39
|
+
end
|
40
|
+
|
41
|
+
def ip_src
|
42
|
+
iphdr.try(:ipaddr_src)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ip_dst
|
46
|
+
iphdr.try(:ipaddr_dst)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module By2
|
2
|
+
module Models
|
3
|
+
|
4
|
+
class Iphdr < ActiveRecord::Base
|
5
|
+
self.table_name = 'iphdr'
|
6
|
+
self.primary_keys = :sid, :cid
|
7
|
+
|
8
|
+
belongs_to :event, :foreign_key => [:sid, :cid]
|
9
|
+
|
10
|
+
|
11
|
+
def self.ip_src_or_dst(ip)
|
12
|
+
where("iphdr.ip_dst = ? or iphdr.ip_src = ?", int32(ip), int32(ip))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.ip_src(ip)
|
16
|
+
where("iphdr.ip_src = ?", int32(ip))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.ip_dst(ip)
|
20
|
+
where("iphdr.ip_dst = ?", int32(ip))
|
21
|
+
end
|
22
|
+
|
23
|
+
def ipaddr_src
|
24
|
+
Utils.int32_to_ip(self.ip_src)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ipaddr_dst
|
28
|
+
Utils.int32_to_ip(self.ip_dst)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def self.int32(ip)
|
34
|
+
Utils.ip_to_int32(ip)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module By2
|
2
|
+
module Models
|
3
|
+
|
4
|
+
class Payload < ActiveRecord::Base
|
5
|
+
self.table_name = 'data'
|
6
|
+
self.primary_keys = :sid, :cid
|
7
|
+
|
8
|
+
belongs_to :event, :foreign_key => [:sid, :cid]
|
9
|
+
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
Utils.hex_to_ascii(self.data_payload)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|