thm 0.4.5 → 0.5.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 40bbc67ea101f1fc34f2f85fdb06c13677451fd0
4
- data.tar.gz: 78a08e73c11da31adf4834dbd5913fbfbb93eb35
3
+ metadata.gz: 8b169ed01c8ea3600372b26c6586e0a972f4c3e8
4
+ data.tar.gz: 20b7c47617ee242e15560f44b245dcfde937b3c7
5
5
  SHA512:
6
- metadata.gz: fcd88da68f3f44c7072378134bd3c2ab644b1db8f07db52d43a1644009d76ba92d8787b4b4c625b47b5199a922b7e80e72e69624766e496abcedae914910c3c4
7
- data.tar.gz: 8ce4134539e86fae9dac11d7e9cfedde5310c80a9088eafb7a94f02d575b3ee5e6c4bb4e183b8409569d329e7aa8b048c21edd1ff2e4938be87381e10777cf0b
6
+ metadata.gz: ddedda3ae04f8f916f557e5e1080cdf2173a1aff66756f1b3d467fa495ee8f1fcf743fdffdee8833014f89466798028831e96eb326e0133705fb53544d1d21be
7
+ data.tar.gz: 3ce9517ec13223cb8573d5ef9026185b37535c63dd56ed86d3e65f013605f3571e48a76c007e272670153dfbcebb5b571c9877d0982c398056bf8d1d6c754360
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  Threatmonitor - Packet Analysis suite with MonetDB / MySQL - RabbitMQ & PCap integration
3
-
3
+ ========================================================================================
4
4
 
5
5
  `,`,`,`
6
6
  ------
data/Rakefile CHANGED
@@ -44,6 +44,8 @@ Gem::Specification.new do |spec|
44
44
  "lib/thm/version.rb",
45
45
  "lib/thm/dataservices/geolocation/geolocation.rb",
46
46
  "lib/thm/dataservices/trafviz/trafviz.rb",
47
+ "lib/thm/dataservices/external.rb",
48
+ "lib/thm/dataservices/safebrowsing_api.rb",
47
49
  "js/jquery.min.js",
48
50
  "js/chartkick.js",
49
51
  "js/JSXTransformer.js",
@@ -16,6 +16,7 @@ require 'chartkick'
16
16
  require 'sinatra/base'
17
17
  require 'slim'
18
18
  require 'colorize'
19
+ require 'keycounter'
19
20
 
20
21
  require File.expand_path(File.join(
21
22
  File.dirname(__FILE__),
@@ -39,42 +40,6 @@ class Sinatra::Base
39
40
 
40
41
  end
41
42
 
42
-
43
- class Geocounter
44
-
45
- # Create / Add to instance variable
46
- def geocount(country)
47
- country.gsub!(" ", "_") # no spaces or case
48
- if !instance_variable_get("@#{country}")
49
- instance_variable_set("@#{country}", 1)
50
- else
51
- instance_variable_set("@#{country}", instance_variable_get("@#{country}") + 1)
52
- end
53
- puts "Country: #{country} Value: "+instance_variable_get("@#{country}").to_s
54
- end
55
-
56
- # Read a single country
57
- def geocount_reader(country)
58
- country.gsub!(" ", "_") # no spaces or case
59
- valnum = instance_variable_get("@#{country}")
60
- return valnum
61
- end
62
-
63
- # Compile in array with the totals of all instance variables
64
- def geocount_compile
65
- countrycounts = Array.new
66
- # You can't really inherit this class as the other class may also contain instance variables
67
- # its not really an exact logic this class only works alone.
68
- instance_variables.each {|n|
69
- t = n.to_s.gsub("@", "")
70
- countrycounts << ["#{t}", instance_variable_get("#{n}")]
71
- }
72
- return countrycounts
73
- countrycounts
74
- end
75
-
76
- end
77
-
78
43
  module ThmUI extend self
79
44
 
80
45
  class Deedrah < Sinatra::Base
@@ -204,14 +169,14 @@ module ThmUI extend self
204
169
  query = "select count(*) as num2, ip_dst from wifi_ippacket a JOIN wifi_#{proto}packet b on (a.guid = b.guid) JOIN service_definitions s on (s.num = b.#{proto}_dport) where #{proto}_dport > 0 and #{proto}_dport < 10000 and s.protocol = '#{proto.upcase}' and ip_dst not in ('255.255.255.255') group by b.#{proto}_dport, a.ip_dst;"
205
170
  resusrcnt = @sessobj.query("#{query}")
206
171
  rowusrcnt = Array.new
207
- @gcnt = Geocounter.new
172
+ @gcnt = Keycounter.new
208
173
  while row = resusrcnt.fetch_hash do
209
174
  num2 = row["num2"].to_s
210
175
  ip_dst = row["ip_dst"].to_s
211
176
  location = geoiplookup(ip_dst)
212
177
  locfix = location.to_s.gsub("(", "").gsub(")", "") # Yawn
213
178
  if location != nil or location == "" # You can't have a blank or nil instance_variable
214
- @gcnt.geocount("#{locfix}")
179
+ @gcnt.keycount("#{locfix}")
215
180
  end
216
181
  rowusrcnt << ["#{ip_dst} #{location}", "#{num2}"]
217
182
  end
@@ -259,7 +224,7 @@ module ThmUI extend self
259
224
  @rowusrcnt5 = toptalkers("udp")
260
225
  # Top UDP/IP Talkers
261
226
  @rowusrcnt6 = toptalkers("tcp")
262
- @rowgeocount = @gcnt.geocount_compile
227
+ @rowgeocount = @gcnt.keycount_compile
263
228
  puts "Geo Data:"
264
229
  @rowgeocount.each {|n, x|
265
230
  puts "#{n} #{x}"
@@ -15,6 +15,7 @@ require 'walltime'
15
15
  require 'readline'
16
16
  require 'mymenu'
17
17
  require 'pp'
18
+ require 'cgi'
18
19
 
19
20
  require File.expand_path(File.join(
20
21
  File.dirname(__FILE__),
@@ -80,6 +81,7 @@ opts = GetoptLong.new(
80
81
  [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
81
82
  [ '--interface', '-i', GetoptLong::OPTIONAL_ARGUMENT],
82
83
  [ '--snaplength', '-s', GetoptLong::OPTIONAL_ARGUMENT],
84
+ [ '--safebrowsing', '-b', GetoptLong::OPTIONAL_ARGUMENT],
83
85
  [ '--debug', '-d', GetoptLong::NO_ARGUMENT ],
84
86
  )
85
87
 
@@ -94,6 +96,8 @@ opts.each do |opt, arg|
94
96
 
95
97
  -s, --snaplength - Snaplength [ OPTIONAL_ARGUMENT ]
96
98
 
99
+ -b, --safebrowsing - Enable Google Safebrowsing API for traffic categorization
100
+
97
101
  -d --debug
98
102
 
99
103
  ]
@@ -106,6 +110,8 @@ opts.each do |opt, arg|
106
110
  @interface = nil || arg
107
111
  when '--snaplength'
108
112
  @snaplength = nil || arg
113
+ when '--safebrowsing'
114
+ @safebrowsing_enabled = nil || arg
109
115
  end
110
116
  end
111
117
 
@@ -113,13 +119,14 @@ puts banner
113
119
 
114
120
  # Trafviz DataServices
115
121
  tv = Thm::DataServices::Trafviz.new
122
+ tv.debug = @debug
116
123
  tv.reqtable = HTTP_REQUEST_TABLE
117
124
  tv.reqtableua = HTTP_REQUEST_TABLE_UA
118
125
  # Connect to Datastore
119
126
  gloc = Thm::DataServices::Geolocation.new
120
127
  gloc.datastore = DATASTORE
121
- gloc.debug = false
122
- gloc.autocommit = true
128
+ gloc.debug = 0
129
+ gloc.autocommit = false
123
130
  gloc.dbhost = DBHOST
124
131
  gloc.dbuser = DBUSER
125
132
  gloc.dbpass = DBPASS
@@ -128,152 +135,12 @@ gloc.dbconnect
128
135
 
129
136
  use_const_defined_unless?("INTERFACE")
130
137
  use_const_defined_unless?("SNAPLENGTH")
138
+ use_const_defined_unless?("SAFEBROWSING_ENABLED")
131
139
 
132
140
  startup = "-s #{@snaplength} -n -i #{@interface}"
133
141
  puts "Trafviz - Startup Parameters: #{startup}"
142
+ puts "Safebrowsing URL: #{SAFEBROWSING_URL}" unless @safebrowsing_enabled == "false"
134
143
 
135
- module Thm
136
-
137
- class DataServices::Trafviz::FilterManager
138
-
139
- attr_reader :bookmarks, :pcapsetfilter
140
-
141
- def initialize
142
- @bookmarks = Array.new
143
- @bkm = MyMenu.new
144
- @bkm.settitle("Welcome to Trafviz")
145
- @bkm.mymenuname = "Trafviz"
146
- @bkm.prompt = "Trafviz"
147
- @pcapsetfilter = String.new
148
- end
149
-
150
- def read(file)
151
- b = 0
152
- File.open("#{Dir.home}/.thm/#{file}", 'r') {|n|
153
- n.each_line {|l|
154
- puts "\e[1;36m#{b})\e[0m\ #{l}"
155
- @bookmarks[b] = l
156
- b += 1
157
- }
158
- }
159
- end
160
-
161
- def write(file)
162
- @bkm.mymenuname = "Filters"
163
- @bkm.prompt = "\e[1;33m\Set filter>\e[0m\ "
164
- pcapfilter = @bkm.definemenuitem("selectfilter", true) do
165
- # Just needs value returned via readline block into addfilter
166
- end
167
- fltvalid = validate_filter?("#{pcapfilter}")
168
- if fltvalid == true
169
- File.open("#{Dir.home}/.thm/#{file}", 'a') {|n| # Append to filter file
170
- n.puts("#{addfilter}")
171
- }
172
- end
173
- end
174
-
175
- def set_defaults(file)
176
- # Add default example filters
177
- File.open("#{Dir.home}/.thm/#{file}", 'w') {|n|
178
- n.puts("webtraffic: tcp dst port 80")
179
- n.puts("sourceportrange: tcp src portrange 1024-65535")
180
- }
181
- end
182
-
183
- def validate_filter?(filter)
184
- begin
185
- Pcap::Filter.compile("#{filter}")
186
- puts "Filter Compile #{filter}"
187
- return true
188
- rescue Pcap::PcapError => e
189
- pp e
190
- return false
191
- end
192
- end
193
-
194
- def build_filter_menu
195
- @bkm.settitle("Welcome to Trafviz")
196
- @bkm.mymenuname = "Trafviz"
197
- @bkm.prompt = "Trafviz"
198
- @bkm.debug = 3
199
- pp @bookmarks
200
- @bookmarks.each {|n|
201
- func_name = n.split(":")[0]
202
- pcap_filter = n.split(":")[1].lstrip
203
- puts "#{pcap_filter}"
204
- # Instance Eval probably nicer
205
- fltvalid = validate_filter?("#{pcap_filter}") # Because validate_filter? won't exist inside instance_eval
206
- @bkm.instance_eval do
207
- pp fltvalid
208
- if fltvalid == true
209
- definemenuitem("#{func_name}") do
210
- @pcapsetfilter = "#{pcap_filter}"
211
- #thm = DataServices::Trafviz::Main.new
212
- end
213
- additemtolist("#{func_name}: #{pcap_filter}", "#{func_name};")
214
- end
215
- end
216
- }
217
- @bkm.instance_eval do
218
- definemenuitem("showfilter") do
219
- puts "Filter: #{@pcapsetfilter}"
220
- end
221
- additemtolist("Show Current Filter", "showfilter;")
222
- end
223
- @bkm.additemtolist("Display Menu", "showmenu;")
224
- @bkm.additemtolist("Toggle Menu", "togglemenu;")
225
- @bkm.additemtolist("Exit Trafviz", "exit;")
226
- @bkm.menu!
227
- end
228
-
229
- def load_filters(file)
230
- if File.exists?("#{Dir.home}/.thm/#{file}")
231
- read(file)
232
- else
233
- set_defaults(file)
234
- read(file)
235
- end
236
- build_filter_menu
237
- end
238
-
239
- end
240
-
241
- end
242
-
243
- # Main class / Startup
244
-
245
- module Thm
246
-
247
- class DataServices::Trafviz::Main
248
-
249
- attr_accessor :startup
250
-
251
- def initialize
252
- @filter_const = Array.new
253
- @startup = String.new
254
- @thm = Thm::DataServices::Trafviz::FilterManager.new
255
- end
256
-
257
- def addfilter(const, filter)
258
- if @thm.validate_filter?(filter) == true
259
- filtercode = %Q{#{const} = Pcap::Filter.new('#{filter}', @trafviz.capture)}
260
- @filter_const << "#{const})"
261
- eval(filtercode)
262
- end
263
- end
264
-
265
- def commitfilters
266
- flts = @filter_const.join(" | ") # Build string of CONST names
267
- commitcode = %Q{@trafviz.add_filter(#{flts})}
268
- eval(flts)
269
- end
270
-
271
- def run!
272
- @trafviz = Pcaplet.new(@startup)
273
- end
274
- end
275
-
276
- end
277
144
  =begin
278
145
  FILTERLIST = 'filters.lst'
279
146
  a = Thm::DataServices::Trafviz::FilterManager.new
@@ -291,6 +158,8 @@ a.menu!
291
158
  HTTP_REQUEST = Pcap::Filter.new('tcp dst port 80', @trafviz.capture)
292
159
  HTTP_RESPONSE = Pcap::Filter.new('tcp src portrange 1024-65535', @trafviz.capture)
293
160
 
161
+ @sb = Thm::DataServices::Safebrowsing.new unless @safebrowsing_enabled == "false"
162
+
294
163
  @trafviz.add_filter(HTTP_REQUEST | HTTP_RESPONSE)
295
164
  @trafviz.each_packet {|pkt|
296
165
  data = pkt.tcp_data.to_s
@@ -308,6 +177,11 @@ HTTP_RESPONSE = Pcap::Filter.new('tcp src portrange 1024-65535', @trafviz.captur
308
177
  puts "\e[4;36mGeo Location:\e[0m\ \n\e[0;35m#{geo} \e[0m\ "
309
178
  puts "\e[4;36mRequest Data:\e[0m\ \n\e[0;32m#{data_highlight} \e[0m\ "
310
179
  tv.makeurl(data_orig)
180
+ makeurl_last = CGI.escape(tv.makeurl_last)
181
+ if instance_variable_defined?("@sb")
182
+ @sb.debug = @debug
183
+ @sb.lookup("#{SAFEBROWSING_URL}#{makeurl_last}")
184
+ end
311
185
  # Process data and prepare then send elsewhere
312
186
  query_return_sql = tv.request_filter(data)
313
187
  # Store data into Datastore
@@ -319,7 +193,7 @@ HTTP_RESPONSE = Pcap::Filter.new('tcp src portrange 1024-65535', @trafviz.captur
319
193
  end
320
194
  }
321
195
  rescue
322
- Tools::log_errors("/tmp/thm-sql-errors.log", "SQL Error - #{Time.now} - #{query_return_sql}") # Catch them all
196
+ Tools::log_errors("/tmp/thm-sql-errors.log", "SQL Error - #{Time.now} - #{query_return_sql}") unless query_return_sql == "SELECT 1;"
323
197
  end
324
198
  stwt.watch('stop')
325
199
  stwt.print_stats
@@ -338,4 +212,11 @@ HTTP_RESPONSE = Pcap::Filter.new('tcp src portrange 1024-65535', @trafviz.captur
338
212
  end
339
213
  end
340
214
  puts s.gsub("GET", "\e[1;36mGET\e[0m").gsub("POST", "\e[1;36mPOST\e[0m") if s
215
+
216
+ # Just so we don't loose any data between commits on exiting...
217
+ trap("INT") {
218
+ gloc.commit
219
+ exit
220
+ }
221
+
341
222
  }
@@ -78,9 +78,8 @@ if obj.user_exists?("admin") == true
78
78
  end
79
79
  else
80
80
  # Create admin user if none exists
81
- #obj.add_user
82
- #obj.delete_user
83
- #obj.set_password
81
+ obj.add_user
82
+ obj.set_password
84
83
  end
85
84
 
86
85
 
data/config.rb CHANGED
@@ -31,11 +31,24 @@ module Thm
31
31
  HTTP_REQUEST_TABLE = "http_traffic_json"
32
32
  HTTP_RESPONSE_TABLE = "http_traffic_json"
33
33
  HTTP_REQUEST_TABLE_UA = "http_traffic_ua"
34
-
34
+ # Common HTTP / HTTPS data ports for Trafviz
35
+ HTTP_PORTS = [80, 3128, 8000, 8080, 8088, 8888]
36
+ HTTPS_PORTS = [443, 444, 3129, 8443]
37
+
35
38
  # Misc
36
39
  SNAPLENGTH = 65536
37
40
  INTERFACE = "eth0"
41
+
42
+ # Google Safe Browsing API
43
+ SAFEBROWSING_ENABLED = "false"
44
+ SAFEBROWSING_API_KEY = "12345"
45
+ GOOGLE_API_PROJECTNAME = "myproject"
46
+ SAFEBROWSING_URL = "https://sb-ssl.google.com/safebrowsing/api/lookup?client=#{GOOGLE_API_PROJECTNAME}&key=#{SAFEBROWSING_API_KEY}&appver=1.5.2&pver=3.1&url="
38
47
 
48
+ GEOCODING_ENABLED = "false"
49
+ GEOCODING_API_KEY = "12345"
50
+ GEOCODING_URL = "https://maps.googleapis.com/maps/api/geocode/json?key=#{GEOCODING_API_KEY}&" # Format: "latlng=40.714224,-73.961452"
51
+
39
52
  end
40
53
 
41
54
  end
data/lib/thm.rb CHANGED
@@ -27,6 +27,126 @@ class String
27
27
 
28
28
  end
29
29
 
30
+
31
+ # Slight patch here
32
+ # Needs to moving to monkeypatches.rb
33
+
34
+ class MonetDBConnection
35
+
36
+ # perform a real connection; retrieve challenge, proxy through merovinginan, build challenge and set the timezone
37
+ def real_connect
38
+
39
+ server_challenge = retrieve_server_challenge()
40
+ if server_challenge != nil
41
+ salt = server_challenge.split(':')[0]
42
+ @server_name = server_challenge.split(':')[1]
43
+ @protocol = server_challenge.split(':')[2].to_i
44
+ @supported_auth_types = server_challenge.split(':')[3].split(',')
45
+ @server_endianness = server_challenge.split(':')[4]
46
+ =begin
47
+ Causes issues with Threatmonitor
48
+ #if @@SUPPORTED_PROTOCOLS.include?(@protocol) == False
49
+ # raise MonetDBProtocolError, "Protocol not supported. The current implementation of ruby-monetdb works with MAPI protocols #{@@SUPPORTED_PROTOCOLS} only."
50
+ #end
51
+ =end
52
+ @pwhash = server_challenge.split(':')[5]
53
+ else
54
+ raise MonetDBConnectionError, "Error: server returned an empty challenge string."
55
+ end
56
+
57
+ # The server supports only RIPMED160 or crypt as an authentication hash function, but the driver does not.
58
+ if @supported_auth_types.length == 1
59
+ auth = @supported_auth_types[0]
60
+ if auth.upcase == "RIPEMD160"
61
+ raise MonetDBConnectionError, auth.upcase + " " + ": algorithm not supported by ruby-monetdb."
62
+ end
63
+ end
64
+
65
+ reply = build_auth_string_v9(@auth_type, salt, @database)
66
+
67
+ if @socket != nil
68
+ @connection_established = true
69
+
70
+ send(reply)
71
+ monetdb_auth = receive
72
+
73
+ if monetdb_auth.length == 0
74
+ # auth succedeed
75
+ true
76
+ else
77
+ if monetdb_auth[0].chr == MSG_REDIRECT
78
+ #redirection
79
+
80
+ redirects = [] # store a list of possible redirects
81
+
82
+ monetdb_auth.split('\n').each do |m|
83
+ # strip the trailing ^mapi:
84
+ # if the redirect string start with something != "^mapi:" or is empty, the redirect is invalid and shall not be included.
85
+ if m[0..5] == "^mapi:"
86
+ redir = m[6..m.length]
87
+ # url parse redir
88
+ redirects.push(redir)
89
+ else
90
+ $stderr.print "Warning: Invalid Redirect #{m}"
91
+ end
92
+ end
93
+
94
+ if redirects.size == 0
95
+ raise MonetDBConnectionError, "No valid redirect received"
96
+ else
97
+ begin
98
+ uri = URI.split(redirects[0])
99
+ # Splits the string on following parts and returns array with result:
100
+ #
101
+ # * Scheme
102
+ # * Userinfo
103
+ # * Host
104
+ # * Port
105
+ # * Registry
106
+ # * Path
107
+ # * Opaque
108
+ # * Query
109
+ # * Fragment
110
+ server_name = uri[0]
111
+ host = uri[2]
112
+ port = uri[3]
113
+ database = uri[5].gsub(/^\//, '') if uri[5] != nil
114
+ rescue URI::InvalidURIError
115
+ raise MonetDBConnectionError, "Invalid redirect: #{redirects[0]}"
116
+ end
117
+ end
118
+
119
+ if server_name == MONETDB_MEROVINGIAN
120
+ if @auth_iteration <= MEROVINGIAN_MAX_ITERATIONS
121
+ @auth_iteration += 1
122
+ real_connect
123
+ else
124
+ raise MonetDBConnectionError, "Merovingian: too many iterations while proxying."
125
+ end
126
+ elsif server_name == MONETDB_MSERVER
127
+ begin
128
+ @socket.close
129
+ rescue
130
+ raise MonetDBConnectionError, "I/O error while closing connection to #{@socket}"
131
+ end
132
+ # reinitialize a connection
133
+ @host = host
134
+ @port = port
135
+
136
+ connect(database, @auth_type)
137
+ else
138
+ @connection_established = false
139
+ raise MonetDBConnectionError, monetdb_auth
140
+ end
141
+ elsif monetdb_auth[0].chr == MSG_INFO
142
+ raise MonetDBConnectionError, monetdb_auth
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ end
149
+
30
150
  module Tools
31
151
 
32
152
  class << self
@@ -56,12 +176,12 @@ module Tools
56
176
  def use_const_defined_unless?(const)
57
177
  const_down = const.downcase
58
178
  if Kernel.const_defined?("#{const}")
59
- if instance_variable_get("@#{const_down}") == nil
179
+ unless instance_variable_defined?("@#{const_down}")
60
180
  instance_variable_set("@#{const_down}", Kernel.const_get("#{const}"))
61
181
  puts "Config Constant #{const}: #{Kernel.const_get("#{const}")}"
62
182
  puts "Instance Variable @#{const_down}: #{instance_variable_get("@#{const_down}")}"
63
183
  else
64
- puts "Param via Getoptlong: Instance Variable #{@const_down}: #{instance_variable_get("@#{const_down}")}"
184
+ puts "Param via Getoptlong: Instance Variable @#{const_down}: #{instance_variable_get("@#{const_down}")}"
65
185
  end
66
186
  else
67
187
  raise "No Config option set add #{const} to your config.rb"
@@ -70,6 +190,24 @@ module Tools
70
190
 
71
191
  end
72
192
 
193
+ module TextProcessing
194
+
195
+
196
+ def text_highlighter(text)
197
+ keys = ["Linux", "Java", "Android", "iPhone", "Mobile", "Chrome",
198
+ "Safari", "Mozilla", "Gecko", "AppleWebKit", "Windows",
199
+ "MSIE", "Win64", "Trident", "wispr", "PHPSESSID", "JSESSIONID",
200
+ "AMD64", "Darwin", "Macintosh", "Mac OS X", "Dalvik", "text/html", "xml"]
201
+ cpicker = [2,3,4,1,7,5,6] # Just a selection of colours
202
+ keys.each {|n|
203
+ text.gsub!("#{n}", "\e[4;3#{cpicker[rand(cpicker.size)]}m#{n}\e[0m\ \e[0;32m".strip)
204
+ }
205
+ return text
206
+ end
207
+
208
+
209
+ end
210
+
73
211
  # Load Database drivers
74
212
  require File.expand_path(File.join(
75
213
  File.dirname(__FILE__),