capra 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/capra +6 -0
- data/lib/capra.rb +83 -0
- data/lib/capra/engine.rb +100 -0
- data/lib/capra/packetgen_extensions.rb +149 -0
- data/lib/capra/private_ips.rb +7 -0
- data/lib/capra/snort_rule_parser.rb +117 -0
- data/lib/capra/version.rb +3 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5c63e457d5d29a1a32e717b9771a1a77c96c2f473e8e2724ccb19b7bcc1a9969
|
4
|
+
data.tar.gz: a2d6115618a11efcab273d51fd49a8c6c2f48b0debc5fe744d9f4242f20a38b9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5fc830199365d7aaa5164856c1c8b4accf135bc553ccbb6366055e9b8edccc4d56fded575a67c7424462ce6ec13c6031a18faba0a089e1756dadb0cf20fe4b97
|
7
|
+
data.tar.gz: f3bcb6939ca831543c5845206a729fab730d03fe395ce31ebd8359d4e74d26c78570a929c259eaaff4111d44422cefb9a96f0362958699f1dcc4250a84eaf005
|
data/bin/capra
ADDED
data/lib/capra.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require "packetgen"
|
2
|
+
require "ipaddr"
|
3
|
+
require "fileutils"
|
4
|
+
require "command_lion"
|
5
|
+
require "capra/version"
|
6
|
+
require "capra/private_ips"
|
7
|
+
require "capra/packetgen_extensions"
|
8
|
+
require "capra/snort_rule_parser"
|
9
|
+
require "capra/engine"
|
10
|
+
require "capra/version"
|
11
|
+
|
12
|
+
require "pry"
|
13
|
+
|
14
|
+
module Capra
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
17
|
+
def self.run_cli!
|
18
|
+
CommandLion::App.run do
|
19
|
+
name "Capra"
|
20
|
+
version Capra::VERSION
|
21
|
+
description "Intrusion Detection System"
|
22
|
+
|
23
|
+
command :init do
|
24
|
+
description "create a base Caprafile in the current working directory"
|
25
|
+
|
26
|
+
action do
|
27
|
+
if File.exists?("Caprafile")
|
28
|
+
puts "error: Caprafile already exists!"
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
File.open("Caprafile", 'w') do |file|
|
32
|
+
file.puts '#!/usr/bin/env ruby'
|
33
|
+
file.puts
|
34
|
+
file.puts "interface '#{Interfacez.default}'"
|
35
|
+
file.puts
|
36
|
+
file.puts "# your rules go here"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
command :start do
|
42
|
+
description "start the engine"
|
43
|
+
|
44
|
+
default "Caprafile"
|
45
|
+
|
46
|
+
action do
|
47
|
+
unless File.exists?(argument)
|
48
|
+
puts "error: cannot find #{argument} in the current directory"
|
49
|
+
puts
|
50
|
+
puts "hint: run `capra init` to create a base Caprafile"
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
Capra::Engine.new(file: argument)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# $ capra convert 'alert tcp any any -> any 21 (msg:"ftp")'
|
59
|
+
# rule 'TCP' do |packet|
|
60
|
+
# next unless packet.tcp.dport == 21
|
61
|
+
# alert "ftp"
|
62
|
+
# end
|
63
|
+
command :convert do
|
64
|
+
description "Convert Snort rule(s) to Caprafile syntax"
|
65
|
+
|
66
|
+
type :string
|
67
|
+
|
68
|
+
action do
|
69
|
+
if File.file?(argument)
|
70
|
+
File.foreach(argument) do |line|
|
71
|
+
line = line.strip
|
72
|
+
next if line.empty?
|
73
|
+
|
74
|
+
Capra::SnortRuleParser.convert(line)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
Capra::SnortRuleParser.convert(argument)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/capra/engine.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Capra
|
2
|
+
class Engine
|
3
|
+
attr_accessor :interface
|
4
|
+
attr_accessor :rules
|
5
|
+
|
6
|
+
def initialize(file: nil, &block)
|
7
|
+
default_interface
|
8
|
+
@rules = {}
|
9
|
+
if file
|
10
|
+
instance_eval File.read(file)
|
11
|
+
else
|
12
|
+
instance_eval &block
|
13
|
+
end
|
14
|
+
start!
|
15
|
+
end
|
16
|
+
|
17
|
+
def interface(iface)
|
18
|
+
@interface = iface
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_interface
|
22
|
+
@interface = Interfacez.default
|
23
|
+
end
|
24
|
+
|
25
|
+
def pcap(file)
|
26
|
+
@pcap = file
|
27
|
+
end
|
28
|
+
|
29
|
+
def save_to(file)
|
30
|
+
@save_to = file
|
31
|
+
end
|
32
|
+
|
33
|
+
def debug!
|
34
|
+
binding.pry
|
35
|
+
end
|
36
|
+
|
37
|
+
def rule(type, description: nil, reference: nil, &block)
|
38
|
+
if @rules[type]
|
39
|
+
@rules[type] << block
|
40
|
+
else
|
41
|
+
@rules[type] = [block]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def alert(mesg)
|
46
|
+
puts mesg
|
47
|
+
end
|
48
|
+
|
49
|
+
def email(recpt)
|
50
|
+
puts "Sending email!"
|
51
|
+
end
|
52
|
+
|
53
|
+
def save(packet)
|
54
|
+
@save_to = "capra-save-"+Time.now.utc.to_s.split(" ").join("-")+".pcapng" if @save_to.nil?
|
55
|
+
|
56
|
+
pf = PacketGen::PcapNG::File.new
|
57
|
+
pf.array_to_file [packet]
|
58
|
+
pf.to_f(@save_to, append: true)
|
59
|
+
end
|
60
|
+
|
61
|
+
def start!
|
62
|
+
if @pcap
|
63
|
+
read_pcap_file(@pcap) do |packet|
|
64
|
+
@rules.each do |header, blocks|
|
65
|
+
if header == 'ANY' || packet.is?(header)
|
66
|
+
blocks.each do |block|
|
67
|
+
block.call(packet)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
else
|
73
|
+
PacketGen.capture(iface: @interface) do |packet|
|
74
|
+
@rules.each do |header, blocks|
|
75
|
+
if header == 'ANY' || packet.is?(header)
|
76
|
+
blocks.each do |block|
|
77
|
+
block.call(packet)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def read_pcap_file(filename)
|
88
|
+
PcapNG::File.new.read_packets(filename) do |packet|
|
89
|
+
yield packet
|
90
|
+
end
|
91
|
+
rescue StandardError => e
|
92
|
+
PCAPRUB::Pcap.open_offline(filename).each_packet do |packet|
|
93
|
+
next unless (packet = PacketGen.parse(packet.to_s))
|
94
|
+
|
95
|
+
yield packet
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module PacketGen
|
2
|
+
class Packet
|
3
|
+
def ftp?
|
4
|
+
return false unless self.is? 'TCP'
|
5
|
+
self.tcp.dport == 21 || self.tcp.sport == 21
|
6
|
+
end
|
7
|
+
|
8
|
+
def ssh?
|
9
|
+
return false unless self.is? 'TCP'
|
10
|
+
self.tcp.dport == 22 || self.tcp.sport == 22
|
11
|
+
end
|
12
|
+
|
13
|
+
def icmp?
|
14
|
+
self.is? 'ICMP'
|
15
|
+
end
|
16
|
+
|
17
|
+
def http?
|
18
|
+
return false unless self.is? 'TCP'
|
19
|
+
self.is? 'HTTP::Request' or self.is? 'HTTP::Response'
|
20
|
+
end
|
21
|
+
|
22
|
+
def https?
|
23
|
+
return false unless self.is? 'TCP'
|
24
|
+
self.tcp.dport == 443 || self.tcp.sport == 443
|
25
|
+
end
|
26
|
+
|
27
|
+
def telnet?
|
28
|
+
return false unless self.is? 'TCP'
|
29
|
+
self.tcp.dport == 23 || self.tcp.sport == 23
|
30
|
+
end
|
31
|
+
|
32
|
+
def dns?
|
33
|
+
return true if self.is? 'DNS'
|
34
|
+
end
|
35
|
+
|
36
|
+
def ip?
|
37
|
+
return true if self.is? 'IP'
|
38
|
+
end
|
39
|
+
|
40
|
+
def arp?
|
41
|
+
return true if self.is? 'ARP'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Header
|
46
|
+
class TCP
|
47
|
+
def port?(int)
|
48
|
+
self.dport == int || self.dport == int
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class DNS
|
53
|
+
def queries
|
54
|
+
return [] unless self.query? || self.response?
|
55
|
+
packet.dns.qd.map { |q| q.name.chop! }
|
56
|
+
end
|
57
|
+
|
58
|
+
def responses
|
59
|
+
return {} unless self.response?
|
60
|
+
info = {}
|
61
|
+
packet.dns.an.map do |a|
|
62
|
+
name = a.name.chop!
|
63
|
+
if info[name]
|
64
|
+
info[name] << a.human_rdata
|
65
|
+
else
|
66
|
+
info[name] = [a.human_rdata]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
info
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class IP
|
74
|
+
def internal_communication_only?
|
75
|
+
PRIVATE_IPS.any? { |private_ip| private_ip.include?(self.src) } and PRIVATE_IPS.any? { |private_ip| private_ip.include?(self.dst) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def external_communication?
|
79
|
+
!internal_communication_only?
|
80
|
+
end
|
81
|
+
|
82
|
+
def internal_destination?
|
83
|
+
PRIVATE_IPS.any? { |private_ip| private_ip.include?(self.dst) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def external_destination?
|
87
|
+
!internal_destination?
|
88
|
+
end
|
89
|
+
|
90
|
+
def internal_source?
|
91
|
+
PRIVATE_IPS.any? { |private_ip| private_ip.include?(self.src) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def external_source?
|
95
|
+
!internal_source?
|
96
|
+
end
|
97
|
+
|
98
|
+
def within_subnet?(cidr)
|
99
|
+
subnet = IPAddr.new(cidr)
|
100
|
+
subnet.include?(self.src) or subnet.include?(self.dst)
|
101
|
+
end
|
102
|
+
|
103
|
+
def from_subnet?(cidr)
|
104
|
+
subnet = IPAddr.new(cidr)
|
105
|
+
subnet.include?(self.src)
|
106
|
+
end
|
107
|
+
|
108
|
+
def from_subnets?(cidrs)
|
109
|
+
cidrs.map { IPAddr.new(cidr) }.include?(self.src)
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_subnet?(cidr)
|
113
|
+
subnet = IPAddr.new(cidr)
|
114
|
+
subnet.include?(self.dst)
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_subnets?(cidr)
|
118
|
+
cidrs.map { IPAddr.new(cidr) }.include?(self.dst)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class ICMP
|
123
|
+
def echo_reply?
|
124
|
+
self.type == 0
|
125
|
+
end
|
126
|
+
|
127
|
+
def destination_unreachable?
|
128
|
+
self.type == 3
|
129
|
+
end
|
130
|
+
|
131
|
+
def redirect?
|
132
|
+
self.type == 5
|
133
|
+
end
|
134
|
+
|
135
|
+
def echo?
|
136
|
+
self.type == 8
|
137
|
+
end
|
138
|
+
|
139
|
+
def router_advertisement?
|
140
|
+
self.type == 9
|
141
|
+
end
|
142
|
+
|
143
|
+
def router_solicitation?
|
144
|
+
self.type == 10
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Capra
|
2
|
+
module SnortRuleParser
|
3
|
+
def self.parse(rule)
|
4
|
+
# alert tcp $EXTERNAL_NET any -> $HOME_NET 21 (msg:"FTP MDTM overflow attempt"; flow:to_server,established; content:"MDTM"; nocase; isdataat:100,relative; pcre:"/^MDTM\s[^\n]{100}/smi"; reference:bugtraq,9751; reference:cve,2001-1021; reference:cve,2004-0330; reference:nessus,12080; classtype:attempted-admin; sid:2546; rev:5;)
|
5
|
+
rule_parts = rule.split
|
6
|
+
|
7
|
+
rule_options = {}
|
8
|
+
|
9
|
+
rule_parts[7..-1].join(" ").sub("(",'').sub(")",'').split(";").map { |opt| opt.split(":").map { |val| val.gsub('"', '') }}.each do |k, v|
|
10
|
+
k = k.strip
|
11
|
+
|
12
|
+
if rule_options[k]
|
13
|
+
rule_options[k] << v
|
14
|
+
else
|
15
|
+
rule_options[k] = [v]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
{
|
20
|
+
action: rule_parts[0],
|
21
|
+
protocol: rule_parts[1],
|
22
|
+
source_ip: rule_parts[2],
|
23
|
+
source_port: rule_parts[3],
|
24
|
+
direction: rule_parts[4], # almost always -> unless you're crazy?
|
25
|
+
destination_ip: rule_parts[5],
|
26
|
+
destination_port: rule_parts[6],
|
27
|
+
options: rule_options
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.convert(rule)
|
32
|
+
parsed_rule = self.parse(rule)
|
33
|
+
puts "rule '#{parsed_rule[:protocol].upcase}' do |packet|"
|
34
|
+
unless parsed_rule[:source_ip] == "any"
|
35
|
+
if parsed_rule[:source_ip] == "$EXTERNAL_NET" # might want to check direction too?
|
36
|
+
puts "\tnext unless packet.ip.external_source?"
|
37
|
+
elsif parsed_rule[:source_ip][0] == "["
|
38
|
+
puts "\tnext unless packet.ip.from_subnets?(#{parsed_rule[:source_ip].sub("[","").sub("]","").split(",").inspect})"
|
39
|
+
else
|
40
|
+
puts "\tnext unless packet.ip.src == '#{parsed_rule[:source_ip]}'"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
unless parsed_rule[:source_port] == "any"
|
44
|
+
puts "\tnext unless packet.#{parsed_rule[:protocol]}.sport == #{parsed_rule[:source_port]}"
|
45
|
+
end
|
46
|
+
unless parsed_rule[:destination_ip] == "any"
|
47
|
+
if parsed_rule[:destination_ip] == "$HOME_NET"
|
48
|
+
puts "\tnext unless packet.ip.internal_destination?"
|
49
|
+
elsif parsed_rule[:source_ip][0] == "["
|
50
|
+
parsed_rule[:source_ip].sub("[","").sub("]","").split(",").each do |cidr|
|
51
|
+
puts "\tnext unless packet.ip.to_subnets?(#{cidr})"
|
52
|
+
end
|
53
|
+
else
|
54
|
+
puts "\tnext unless packet.ip.dst == '#{parsed_rule[:destination_ip]}'"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
unless parsed_rule[:destination_port] == "any"
|
58
|
+
puts "\tnext unless packet.#{parsed_rule[:protocol]}.dport == #{parsed_rule[:destination_port]}"
|
59
|
+
end
|
60
|
+
# TODO: need to support mixed string and byte matching, even though it's insane
|
61
|
+
if parsed_rule[:options]["content"]
|
62
|
+
parsed_rule[:options]["content"].each do |content|
|
63
|
+
if content[0] == "|" and content[-1] == "|"
|
64
|
+
puts "\tnext packet.body.unpack('H*').first.include?(\"#{content.gsub("|","").split.join}\")"
|
65
|
+
else
|
66
|
+
puts "\tnext unless packet.body.include?(\"#{content}\")"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
#puts "\tnext unless packet.body.include?(\"#{parsed_rule[:options]["content"]}\")"
|
70
|
+
end
|
71
|
+
if parsed_rule[:options]["pcre"]
|
72
|
+
parsed_rule[:options]["pcre"].each do |pcre|
|
73
|
+
regex, regex_ops = pcre.split("/")[1..-1]
|
74
|
+
regex_ops_value = regex_ops.split('').map do |str|
|
75
|
+
case str
|
76
|
+
when "i"
|
77
|
+
Regexp::IGNORECASE
|
78
|
+
when "m"
|
79
|
+
Regexp::MULTILINE
|
80
|
+
when "x"
|
81
|
+
Regexp::EXTENDED
|
82
|
+
else
|
83
|
+
0
|
84
|
+
end
|
85
|
+
end.sum
|
86
|
+
puts "\tnext unless packet.body.match(Regexp.new('#{regex}', #{regex_ops_value}))"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
if parsed_rule[:options]['flags']
|
90
|
+
parsed_rule[:options]['flags'].each do |flag_opt|
|
91
|
+
flag_opt.split('').each do |flag|
|
92
|
+
case flag
|
93
|
+
when 'F' #fin
|
94
|
+
puts "\tnext unless packet.tcp.flag_fin?"
|
95
|
+
when 'S' #syn
|
96
|
+
puts "\tnext unless packet.tcp.flag_syn?"
|
97
|
+
when 'R' #rst
|
98
|
+
puts "\tnext unless packet.tcp.flag_rst?"
|
99
|
+
when 'P' #psh
|
100
|
+
puts "\tnext unless packet.tcp.flag_psh?"
|
101
|
+
when 'A' #ack
|
102
|
+
puts "\tnext unless packet.tcp.flag_ack?"
|
103
|
+
when 'U' #urg
|
104
|
+
puts "\tnext unless packet.tcp.flag_urg?"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
# alert probably needs to be the last thing in the rule for it to work properly in this case
|
110
|
+
if parsed_rule[:options]["msg"]
|
111
|
+
puts "\talert \"#{parsed_rule[:options]["msg"].first}\""
|
112
|
+
end
|
113
|
+
puts "end"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kent 'picat' Gruber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: packetgen
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.1.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.1.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: oj
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.7.11
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.7.11
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: command_lion
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.0.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.0.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ipaddr
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.2.2
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.2.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.17'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.17'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 3.8.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.8.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.12.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.12.2
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry-coolline
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 0.2.5
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 0.2.5
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- kgruber1@emich.edu
|
142
|
+
executables:
|
143
|
+
- capra
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- bin/capra
|
148
|
+
- lib/capra.rb
|
149
|
+
- lib/capra/engine.rb
|
150
|
+
- lib/capra/packetgen_extensions.rb
|
151
|
+
- lib/capra/private_ips.rb
|
152
|
+
- lib/capra/snort_rule_parser.rb
|
153
|
+
- lib/capra/version.rb
|
154
|
+
homepage: https://github.com/picatz/capra
|
155
|
+
licenses:
|
156
|
+
- MIT
|
157
|
+
metadata: {}
|
158
|
+
post_install_message: |-
|
159
|
+
Thank you for installing Capra!
|
160
|
+
Make sure to install `libpcap-dev` if you haven't already!
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
requirements:
|
175
|
+
- libpcap-dev
|
176
|
+
rubyforge_project:
|
177
|
+
rubygems_version: 3.0.0.beta1
|
178
|
+
signing_key:
|
179
|
+
specification_version: 4
|
180
|
+
summary: Intrusion detection system.
|
181
|
+
test_files: []
|