dap 0.0.1

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.
Files changed (54) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +55 -0
  5. data/LICENSE +20 -0
  6. data/README.md +15 -0
  7. data/bin/dap +137 -0
  8. data/dap.gemspec +42 -0
  9. data/data/.gitkeep +0 -0
  10. data/lib/dap.rb +101 -0
  11. data/lib/dap/filter.rb +8 -0
  12. data/lib/dap/filter/base.rb +37 -0
  13. data/lib/dap/filter/geoip.rb +72 -0
  14. data/lib/dap/filter/http.rb +173 -0
  15. data/lib/dap/filter/names.rb +151 -0
  16. data/lib/dap/filter/openssl.rb +53 -0
  17. data/lib/dap/filter/recog.rb +23 -0
  18. data/lib/dap/filter/simple.rb +340 -0
  19. data/lib/dap/filter/udp.rb +401 -0
  20. data/lib/dap/input.rb +74 -0
  21. data/lib/dap/input/csv.rb +60 -0
  22. data/lib/dap/input/warc.rb +81 -0
  23. data/lib/dap/output.rb +117 -0
  24. data/lib/dap/proto/addp.rb +0 -0
  25. data/lib/dap/proto/dtls.rb +21 -0
  26. data/lib/dap/proto/ipmi.rb +94 -0
  27. data/lib/dap/proto/natpmp.rb +19 -0
  28. data/lib/dap/proto/wdbrpc.rb +58 -0
  29. data/lib/dap/utils/oui.rb +16586 -0
  30. data/lib/dap/version.rb +3 -0
  31. data/samples/http_get_reply.ic12.bz2 +0 -0
  32. data/samples/http_get_reply.ic12.sh +1 -0
  33. data/samples/http_get_reply_iframes.json.bz2 +0 -0
  34. data/samples/http_get_reply_iframes.json.sh +1 -0
  35. data/samples/http_get_reply_links.json.sh +1 -0
  36. data/samples/iawide.warc.bz2 +0 -0
  37. data/samples/iawide_warc.sh +1 -0
  38. data/samples/ipmi_chan_auth_replies.crd.bz2 +0 -0
  39. data/samples/ipmi_chan_auth_replies.sh +1 -0
  40. data/samples/ssl_certs.bz2 +0 -0
  41. data/samples/ssl_certs_geo.sh +1 -0
  42. data/samples/ssl_certs_names.sh +1 -0
  43. data/samples/ssl_certs_names_expanded.sh +1 -0
  44. data/samples/ssl_certs_org.sh +1 -0
  45. data/samples/udp-netbios.csv.bz2 +0 -0
  46. data/samples/udp-netbios.sh +1 -0
  47. data/spec/dap/proto/ipmi_spec.rb +19 -0
  48. data/tools/geo-ip-summary.rb +149 -0
  49. data/tools/ipmi-vulns.rb +27 -0
  50. data/tools/json-summarize.rb +81 -0
  51. data/tools/netbios-counts.rb +271 -0
  52. data/tools/upnp-vulns.rb +35 -0
  53. data/tools/value-counts-to-md-table.rb +23 -0
  54. metadata +264 -0
@@ -0,0 +1,3 @@
1
+ module Dap
2
+ VERSION = "0.0.1"
3
+ end
Binary file
@@ -0,0 +1 @@
1
+ bzcat http_get_reply.ic12.bz2 | ../bin/dap lines + field_split_tab line + rename line.f1=ip line.f4=data + select ip data + transform data=qprintdecode + decode_http_reply data + select ip data.http_code data.http_server + json
@@ -0,0 +1 @@
1
+ bzcat http_get_reply_iframes.json.bz2 | ../bin/dap json + transform data=base64decode + include data='<iframe' + html_iframes data + select ip iframe + json
@@ -0,0 +1 @@
1
+ bzcat http_get_reply_iframes.json.bz2 | ../bin/dap json + transform data=base64decode + html_links data + select ip link element + decode_uri link + json
Binary file
@@ -0,0 +1 @@
1
+ bzcat iawide.warc.bz2 | ../bin/dap warc + html_links content + select ip link element + decode_uri link + json
@@ -0,0 +1 @@
1
+ bzcat ipmi_chan_auth_replies.crd.bz2 | ../bin/dap lines + field_split_tab line + rename line.f2=ip line.f6=data + select ip data + transform data=hexdecode + decode_ipmi_chan_auth_reply data + remove data + json
Binary file
@@ -0,0 +1 @@
1
+ bzcat ssl_certs.bz2 | ../bin/dap json + select host_ip ssl_version port cipher + geo_ip host_ip + json
@@ -0,0 +1 @@
1
+ bzcat ssl_certs.bz2 | ../bin/dap json + field_split_array certs + transform certs.f1=base64decode + remove certs + decode_x509 certs.f1 + select certs.f1.names + exists certs.f1.names + split_array certs.f1.names + select certs.f1.names.item + exists certs.f1.names.item + lines
@@ -0,0 +1 @@
1
+ bzcat ssl_certs.bz2 | ../bin/dap json + field_split_array certs + transform certs.f1=base64decode + remove certs + decode_x509 certs.f1 + select certs.f1.names + exists certs.f1.names + split_array certs.f1.names + select certs.f1.names.item + exists certs.f1.names.item + rename certs.f1.names.item=hostname + extract_hostname hostname + select hostname.hostname + split_domains hostname.hostname + select hostname.hostname.domain + rename hostname.hostname.domain=name + prepend_subdomains name=www,dns,mail,vpn,secure,ssl + json
@@ -0,0 +1 @@
1
+ bzcat ssl_certs.bz2 | ../bin/dap json + select host_ip ssl_version port cipher + geo_ip_org host_ip + json
Binary file
@@ -0,0 +1 @@
1
+ bzcat udp-netbios.csv.bz2 | ../bin/dap csv - header=y + select saddr data + rename saddr=ip + transform data=hexdecode + decode_netbios_status_reply data + remove data + geo_ip ip + json
@@ -0,0 +1,19 @@
1
+ require 'bit-struct'
2
+ require_relative '../../../lib/dap/proto/ipmi'
3
+
4
+ module Dap
5
+ module Proto
6
+ module IPMI
7
+
8
+ describe Channel_Auth_Reply do
9
+ it "valid with the proper rmcp version and message length" do
10
+ expect(subject.valid?).to be_false
11
+ expect(Channel_Auth_Reply.new(rmcp_version: 6).valid?).to be_false
12
+ expect(Channel_Auth_Reply.new(message_length: 16).valid?).to be_false
13
+ expect(Channel_Auth_Reply.new(rmcp_version: 6, message_length: 16).valid?).to be_true
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env ruby
2
+ require 'oj'
3
+ require 'optparse'
4
+
5
+ class GeoIPSummary
6
+ attr_accessor :country_name, :region_name, :city_name, :tree
7
+ #
8
+ # Pass the hash keys for the country name, region name and
9
+ # city name that we'll encounter during the process_hash function.
10
+ #
11
+ def initialize(country_name, region_name, city_name)
12
+ @country_name = country_name
13
+ @region_name = region_name
14
+ @city_name = city_name
15
+ @tree = {}
16
+ @tree['count'] = 0
17
+ end
18
+
19
+ def process_hash( json_hash )
20
+ country = json_hash[@country_name]
21
+ region = json_hash[@region_name] || 'Undefined Region'
22
+ city = json_hash[@city_name] || 'Undefined City'
23
+
24
+ # Create subhashes and values as needed on down the tree
25
+ @tree[country] ||= {}
26
+ @tree[country]['count'] ||=0
27
+ @tree[country][region] ||= {}
28
+ @tree[country][region]['count'] ||= 0
29
+ @tree[country][region][city] ||= 0
30
+
31
+ # Now increment counters
32
+ @tree['count'] += 1
33
+ @tree[country]['count'] += 1
34
+ @tree[country][region]['count'] +=1
35
+ @tree[country][region][city] += 1
36
+ end
37
+
38
+ # Performs the final sorting of the hash, with descending order of counts
39
+ #
40
+ def order_tree
41
+ @tree.each do | country, country_hash|
42
+ if country != 'count'
43
+ country_hash.each do | region, region_hash |
44
+ @tree[country][region] = order_hash(@tree[country][region]) if region != 'count'
45
+ end
46
+ @tree[country] = order_hash(@tree[country])
47
+ end
48
+ end
49
+ @tree = order_hash(@tree)
50
+ end
51
+
52
+ private
53
+
54
+ # Sorts the hash, and returns a copy of the hash in sorted order by their counts, or if
55
+ # counts are equal then by their names.
56
+ def order_hash(h)
57
+ keys = h.keys.sort { | k1,k2 |
58
+ if k1 == 'count'
59
+ ret = -1
60
+ elsif k2 == 'count'
61
+ ret = 1
62
+ else
63
+ # Cities level is slightly different form, if hash at this level then compare
64
+ # count value within hash, otherwise just compare values. mult by -1 to reverse
65
+ # ordering
66
+ if h[k1].class == Hash
67
+ ret = ( h[k1]['count'] <=> h[k2]['count'] ) * -1
68
+ ret = k1 <=> k2 if ret == 0 && k1!=nil && k2!=nil
69
+ else
70
+ ret = ( h[k1] <=> h[k2] ) * -1
71
+ ret = k1 <=> k2 if ret == 0 && k1!=nil && k2!=nil
72
+ end
73
+ end
74
+ ret
75
+ }
76
+
77
+ # build up return hash
78
+ ret_hash = {}
79
+ keys.each do | key |
80
+ ret_hash[key] = h[key]
81
+ end
82
+
83
+ ret_hash
84
+ end
85
+ end
86
+ HELP=<<EOF
87
+ This script is used to summarize geoip data from data in a json file. The name of the json element for
88
+ the country, region, and city must be provided. The output is a hash with the country/region/city data and
89
+ the count of occurrences from the input file; this output hash is sorted in count descending order so that
90
+ the most common country, region within a country, and city within a region is returned first.
91
+
92
+ Example with dap:
93
+ bzcat ../samples/ssl_certs.bz2 | ../bin/dap json + select host_ip + geo_ip host_ip + json | ./geo-ip-summary.rb --var host_ip > /tmp/ssl_geo.json
94
+ EOF
95
+
96
+ def parse_command_line(args)
97
+
98
+ options={
99
+ :country => nil,
100
+ :region => nil,
101
+ :city => nil,
102
+ :var => nil
103
+ }
104
+
105
+ OptionParser.new do | opts |
106
+ opts.banner = HELP
107
+ opts.separator ''
108
+
109
+ opts.separator 'GeoIP name options:'
110
+
111
+ opts.on( '--country country_key', 'The name of json key for the country.') do | val |
112
+ options[:country] = val
113
+ end
114
+
115
+ opts.on( '--region region_key', 'The name of the json key for the region.') do | val |
116
+ options[:region] = val
117
+ end
118
+
119
+ opts.on( '--city city_key', 'The name of the json key for the city.' ) do | val |
120
+ options[:city] = val
121
+ end
122
+
123
+ opts.on('--var top-level-var', 'Sets the top level json name, for defining all of country/region/city') do | val |
124
+ options[:var] = val
125
+ options[:country] = "#{val}.country_name"
126
+ options[:region] = "#{val}.region"
127
+ options[:city] = "#{val}.city"
128
+ end
129
+
130
+ opts.on_tail('-h', '--help', 'Show this message') do
131
+ puts opts
132
+ exit(0)
133
+ end
134
+ opts.parse!(args)
135
+ options
136
+ end
137
+ options
138
+ end
139
+ opts = parse_command_line(ARGV)
140
+ raise 'Need json key names for country,region and city.' if opts[:country].nil? || opts[:region].nil? || opts[:city].nil?
141
+
142
+ summarizer = GeoIPSummary.new(opts[:country], opts[:region], opts[:city])
143
+ while line=gets
144
+ summarizer.process_hash(Oj.load(line.strip))
145
+ end
146
+
147
+ Oj.default_options={:indent=>2}
148
+
149
+ puts Oj.dump(summarizer.order_tree)
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'oj'
4
+
5
+ SEARCHES = {
6
+ "data.ipmi_compat_password" => { value: "1", name: "straight-pass" },
7
+ "data.ipmi_compat_md2" => { value: "1", name: "md2" },
8
+ "data.ipmi_compat_none" => { value: "1", name: "noauth" },
9
+ "data.ipmi_user_disable_message_auth" => { value: "1", name: "permsg" },
10
+ "data.ipmi_user_disable_user_auth" => { value: "1", name: "usrlvl" }
11
+ }
12
+
13
+ def search(hash)
14
+ SEARCHES.each do | key, vuln |
15
+ if hash[key] == vuln[:value]
16
+ hash["VULN-IPMI-#{vuln[:name].upcase}"] = "true"
17
+ end
18
+ end
19
+ if (hash['data.ipmi_user_non_null'] == "0") && (hash['data.ipmi_user_null'] == "0")
20
+ hash["VULN-IPMI-ANON"] = "true"
21
+ end
22
+ hash
23
+ end
24
+
25
+ while line=gets
26
+ puts Oj.dump(search(Oj.load(line.strip)))
27
+ end
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ require 'oj'
3
+ require 'optparse'
4
+
5
+ HELP=<<EOF
6
+ This script is used to locate the frequency of a given key in a json document. It will
7
+ inspect and increment the frequency count for each instance of the key found in the json
8
+ document, then order them in descending order and output a json document with the top n
9
+ occurrences of the key value.
10
+
11
+ Note that if passed a key that has unique values, this script can consume a lot of memory.
12
+
13
+ Sample:
14
+ unpigz -c /tmp/2014-05-05-mssql-udp-decoded.json.gz | ruby ~/src/dap/tools/json-summarize.rb --top 20 --key data.mssql.Version
15
+ EOF
16
+
17
+ def parse_command_line(args)
18
+
19
+ options={
20
+ :key => nil,
21
+ :number => nil
22
+ }
23
+
24
+ OptionParser.new do | opts |
25
+ opts.banner = HELP
26
+ opts.separator ''
27
+
28
+ opts.separator 'GeoIP name options:'
29
+
30
+ opts.on( '--key keyname', 'The name of json key to be summarized.') do | val |
31
+ options[:key] = val
32
+ end
33
+
34
+ opts.on( '--top num_items', 'Return top n occurrences.') do | val |
35
+ options[:number] = val.to_i
36
+ end
37
+
38
+ opts.on_tail('-h', '--help', 'Show this message') do
39
+ puts opts
40
+ exit(0)
41
+ end
42
+ opts.parse!(args)
43
+ options
44
+ end
45
+ options
46
+ end
47
+
48
+ # Sorts the hash in descending numerical value for the values
49
+ # part of the hash, returning the sorted hash.
50
+ #
51
+ def order_hash(h)
52
+ keys = h.keys.sort { | k1,k2 |
53
+ ret = ( h[k1] <=> h[k2] ) * -1
54
+ ret = k1 <=> k2 if ret == 0 && k1!=nil && k2!=nil
55
+ ret
56
+ }
57
+ # build up return hash
58
+ ret_hash = {}
59
+ keys.each do | key |
60
+ ret_hash[key] = h[key]
61
+ end
62
+
63
+ ret_hash
64
+ end
65
+
66
+
67
+
68
+
69
+ summary={}
70
+ opts = parse_command_line(ARGV)
71
+ key = opts[:key]
72
+
73
+ while line = gets
74
+ val = Oj.load(line.chomp.strip)[key]
75
+ summary[val] ||= 0
76
+ summary[val] += 1
77
+ end
78
+
79
+ summary = Hash[ *order_hash(summary).flatten.slice(0,2*opts[:number]) ]
80
+ puts Oj.dump(summary)
81
+
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'ostruct'
5
+ require 'oj'
6
+ require 'json'
7
+
8
+ options = OpenStruct.new
9
+ options.top_count = 5
10
+ options.exclude_default_counts = false
11
+
12
+ OptionParser.new do |opts|
13
+ opts.banner = "Usage: netbios-counts.rb [options]"
14
+
15
+ opts.on("-c", "--count [NUM]", OptionParser::DecimalInteger,
16
+ "Specify the number of top count results") do |count|
17
+ options.top_count = count if count > 1
18
+ end
19
+
20
+ opts.on("--count-hostnames-containing [TEXT]", "Count hostnames that include the speified text") do |text|
21
+ options.hostname_containing = text
22
+ end
23
+
24
+ opts.on("--exclude-default-counts", "Exclude the provided top counts") do
25
+ options.exclude_default_counts = true
26
+ end
27
+ end.parse!
28
+
29
+ NUM_TOP_RECORDS = options.top_count
30
+
31
+ module Counter
32
+ def count(hash)
33
+ value = countable_value(hash)
34
+ @counts[value] += 1 unless (value.empty? || value == 'UNKNOWN')
35
+ end
36
+
37
+ def top_counts
38
+ [].tap do |counts|
39
+ ordered_by_count.to_a.take(NUM_TOP_RECORDS).each do |values|
40
+ counts << count_hash(values)
41
+ end
42
+ end
43
+ end
44
+
45
+ def ordered_by_count
46
+ Hash[@counts.sort_by{|k, v| v}.reverse]
47
+ end
48
+ end
49
+
50
+ class CompanyNameCounter
51
+ include Counter
52
+
53
+ def initialize
54
+ @counts = Hash.new(0)
55
+ end
56
+
57
+ def countable_value(hash)
58
+ hash['data.netbios_mac_company'].to_s
59
+ end
60
+
61
+ def count_hash(values)
62
+ { 'name' => values[0], 'count' => values[1] }
63
+ end
64
+
65
+ def apply_to(hash)
66
+ hash['top_companies'] = top_counts
67
+ end
68
+ end
69
+
70
+ class NetbiosNameCounter
71
+ include Counter
72
+
73
+ def initialize
74
+ @counts = Hash.new(0)
75
+ end
76
+
77
+ def countable_value(hash)
78
+ hash['data.netbios_hname'].to_s
79
+ end
80
+
81
+ def count_hash(values)
82
+ { 'hostname' => values[0], 'count' => values[1] }
83
+ end
84
+
85
+ def apply_to(hash)
86
+ hash['top_netbios_hostnames'] = top_counts
87
+ end
88
+ end
89
+
90
+ class MacAddressCounter
91
+ include Counter
92
+
93
+ def initialize
94
+ @counts = Hash.new(0)
95
+ end
96
+
97
+ def countable_value(hash)
98
+ address = hash['data.netbios_mac'].to_s
99
+ [].tap do |data|
100
+ unless (address.empty? || address == '00:00:00:00:00:00')
101
+ data << address
102
+ data << hash['data.netbios_hname']
103
+ data << hash['data.netbios_mac_company']
104
+ end
105
+ end
106
+ end
107
+
108
+ def count_hash(values)
109
+ {
110
+ 'mac_address' => values[0][0],
111
+ 'hostname' => values[0][1],
112
+ 'company' => values[0][2],
113
+ 'count' => values[1]
114
+ }
115
+ end
116
+
117
+ def apply_to(hash)
118
+ hash['top_mac_addresses'] = top_counts
119
+ end
120
+ end
121
+
122
+ class GeoCounter
123
+ def initialize
124
+ @cities = Hash.new(0)
125
+ @countries = Hash.new(0)
126
+ @regions = Hash.new(0)
127
+ end
128
+
129
+ def count(hash)
130
+ city = hash['ip.city'].to_s
131
+ country_code = hash['ip.country_code'].to_s
132
+ region = hash['ip.region'].to_s
133
+ region_name = hash['ip.region_name'].to_s
134
+
135
+ @cities[[city, country_code]] += 1 unless city.empty?
136
+ @countries[country_code] += 1 unless country_code.empty?
137
+ @regions[[region, region_name]] += 1 unless region.empty?
138
+ end
139
+
140
+ def top_cities
141
+ [].tap do |counts|
142
+ ordered_cities.to_a.take(NUM_TOP_RECORDS).each do |values|
143
+ counts << {
144
+ 'city' => values[0][0],
145
+ 'country_code' => values[0][1],
146
+ 'count' => values[1]
147
+ }
148
+ end
149
+ end
150
+ end
151
+
152
+ def top_countries
153
+ [].tap do |counts|
154
+ ordered_countries.to_a.take(NUM_TOP_RECORDS).each do |values|
155
+ counts << { 'country_code' => values[0], 'count' => values[1] }
156
+ end
157
+ end
158
+ end
159
+
160
+ def top_regions
161
+ [].tap do |counts|
162
+ ordered_regions.to_a.take(NUM_TOP_RECORDS).each do |values|
163
+ counts << {
164
+ 'region' => values[0][0],
165
+ 'region_name' => values[0][1],
166
+ 'count' => values[1]
167
+ }
168
+ end
169
+ end
170
+ end
171
+
172
+ def ordered_cities
173
+ Hash[@cities.sort_by{|k, v| v}.reverse]
174
+ end
175
+
176
+ def ordered_countries
177
+ Hash[@countries.sort_by{|k, v| v}.reverse]
178
+ end
179
+
180
+ def ordered_regions
181
+ Hash[@regions.sort_by{|k, v| v}.reverse]
182
+ end
183
+
184
+ def apply_to(hash)
185
+ hash['top_cities'] = top_cities unless top_cities.empty?
186
+ hash['top_countries'] = top_countries unless top_countries.empty?
187
+ hash['top_regions'] = top_regions unless top_regions.empty?
188
+ end
189
+ end
190
+
191
+ class SambaCounter
192
+ include Counter
193
+
194
+ def initialize
195
+ @counts = Hash.new(0)
196
+ end
197
+
198
+ def countable_value(hash)
199
+ address = hash['data.netbios_mac'].to_s
200
+ if (address == '00:00:00:00:00:00')
201
+ hash['data.netbios_hname']
202
+ else
203
+ ''
204
+ end
205
+ end
206
+
207
+ def count_hash(values)
208
+ { 'name' => values[0], 'count' => values[1] }
209
+ end
210
+
211
+ def apply_to(hash)
212
+ hash['top_samba_names'] = top_counts
213
+ end
214
+ end
215
+
216
+ class HostnameContainingCounter
217
+ include Counter
218
+
219
+ def initialize(text)
220
+ @text = text
221
+ @counts = Hash.new(0)
222
+ end
223
+
224
+ def countable_value(hash)
225
+ hostname = hash['data.netbios_hname'].to_s
226
+ [].tap do |data|
227
+ if hostname.include?(@text)
228
+ data << hostname
229
+ data << hash['data.netbios_mac_company']
230
+ data << hash['ip.city'].to_s
231
+ data << hash['ip.country_code'].to_s
232
+ data << hash['ip.country_name'].to_s
233
+ end
234
+ end
235
+ end
236
+
237
+ def count_hash(values)
238
+ {
239
+ 'hostname' => values[0][0],
240
+ 'company' => values[0][1],
241
+ 'city' => values[0][2],
242
+ 'country_code' => values[0][3],
243
+ 'country_name' => values[0][4],
244
+ 'count' => values[1]
245
+ }
246
+ end
247
+
248
+ def apply_to(hash)
249
+ hash["hostnames with '#{@text}'"] = top_counts
250
+ end
251
+ end
252
+
253
+ counters = []
254
+ unless options.exclude_default_counts
255
+ counters << CompanyNameCounter.new
256
+ counters << NetbiosNameCounter.new
257
+ counters << MacAddressCounter.new
258
+ counters << GeoCounter.new
259
+ counters << SambaCounter.new
260
+ end
261
+ counters << HostnameContainingCounter.new(options.hostname_containing) unless options.hostname_containing.nil?
262
+
263
+ while line=gets
264
+ hash = Oj.load(line.strip)
265
+ counters.each { |counter| counter.count(hash) }
266
+ end
267
+
268
+ summary = {}
269
+ counters.each { |counter| counter.apply_to(summary) }
270
+
271
+ puts JSON.pretty_generate(summary)