thm 0.4.5 → 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
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__),