codez-tarantula 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.autotest +14 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG +64 -0
  5. data/DSL_EXAMPLES.md +120 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +20 -0
  8. data/README.rdoc +136 -0
  9. data/Rakefile +36 -0
  10. data/ci/rails2.gemfile +4 -0
  11. data/ci/rails3.gemfile +4 -0
  12. data/laf/images/header_bg.jpg +0 -0
  13. data/laf/images/logo.png +0 -0
  14. data/laf/images/tagline.png +0 -0
  15. data/laf/javascripts/jquery-1.2.3.js +3408 -0
  16. data/laf/javascripts/jquery-ui-tabs.js +890 -0
  17. data/laf/javascripts/jquery.tablesorter.js +861 -0
  18. data/laf/javascripts/tarantula.js +10 -0
  19. data/laf/stylesheets/tarantula.css +346 -0
  20. data/lib/relevance/core_extensions/ellipsize.rb +38 -0
  21. data/lib/relevance/core_extensions/file.rb +15 -0
  22. data/lib/relevance/core_extensions/metaclass.rb +78 -0
  23. data/lib/relevance/core_extensions/response.rb +14 -0
  24. data/lib/relevance/core_extensions/test_case.rb +21 -0
  25. data/lib/relevance/tarantula.rb +55 -0
  26. data/lib/relevance/tarantula/attack.rb +22 -0
  27. data/lib/relevance/tarantula/attack_handler.rb +43 -0
  28. data/lib/relevance/tarantula/basic_attack.rb +44 -0
  29. data/lib/relevance/tarantula/crawler.rb +271 -0
  30. data/lib/relevance/tarantula/detail.html.erb +81 -0
  31. data/lib/relevance/tarantula/form.rb +29 -0
  32. data/lib/relevance/tarantula/form_submission.rb +98 -0
  33. data/lib/relevance/tarantula/html_document_handler.rb +42 -0
  34. data/lib/relevance/tarantula/html_report_helper.rb +46 -0
  35. data/lib/relevance/tarantula/html_reporter.rb +111 -0
  36. data/lib/relevance/tarantula/index.html.erb +37 -0
  37. data/lib/relevance/tarantula/invalid_html_handler.rb +27 -0
  38. data/lib/relevance/tarantula/io_reporter.rb +40 -0
  39. data/lib/relevance/tarantula/link.rb +105 -0
  40. data/lib/relevance/tarantula/log_grabber.rb +22 -0
  41. data/lib/relevance/tarantula/rails_integration_proxy.rb +90 -0
  42. data/lib/relevance/tarantula/recording.rb +12 -0
  43. data/lib/relevance/tarantula/response.rb +19 -0
  44. data/lib/relevance/tarantula/result.rb +83 -0
  45. data/lib/relevance/tarantula/test_report.html.erb +32 -0
  46. data/lib/relevance/tarantula/tidy_handler.rb +35 -0
  47. data/lib/relevance/tarantula/transform.rb +21 -0
  48. data/lib/relevance/tarantula/version.rb +5 -0
  49. data/lib/relevance/tasks/tarantula_tasks.rake +42 -0
  50. data/lib/tarantula-rails3.rb +9 -0
  51. data/spec/relevance/core_extensions/ellipsize_spec.rb +19 -0
  52. data/spec/relevance/core_extensions/file_spec.rb +7 -0
  53. data/spec/relevance/core_extensions/response_spec.rb +48 -0
  54. data/spec/relevance/core_extensions/test_case_spec.rb +19 -0
  55. data/spec/relevance/tarantula/attack_handler_spec.rb +29 -0
  56. data/spec/relevance/tarantula/basic_attack_spec.rb +12 -0
  57. data/spec/relevance/tarantula/crawler_spec.rb +409 -0
  58. data/spec/relevance/tarantula/form_spec.rb +50 -0
  59. data/spec/relevance/tarantula/form_submission_spec.rb +171 -0
  60. data/spec/relevance/tarantula/html_document_handler_spec.rb +43 -0
  61. data/spec/relevance/tarantula/html_report_helper_spec.rb +46 -0
  62. data/spec/relevance/tarantula/html_reporter_spec.rb +82 -0
  63. data/spec/relevance/tarantula/invalid_html_handler_spec.rb +33 -0
  64. data/spec/relevance/tarantula/io_reporter_spec.rb +11 -0
  65. data/spec/relevance/tarantula/link_spec.rb +132 -0
  66. data/spec/relevance/tarantula/log_grabber_spec.rb +26 -0
  67. data/spec/relevance/tarantula/rails_integration_proxy_spec.rb +100 -0
  68. data/spec/relevance/tarantula/result_spec.rb +85 -0
  69. data/spec/relevance/tarantula/tidy_handler_spec.rb +58 -0
  70. data/spec/relevance/tarantula/transform_spec.rb +20 -0
  71. data/spec/relevance/tarantula_spec.rb +23 -0
  72. data/spec/spec_helper.rb +43 -0
  73. data/tarantula.gemspec +25 -0
  74. data/template/tarantula_test.rb +22 -0
  75. data/vendor/xss-shield/MIT-LICENSE +20 -0
  76. data/vendor/xss-shield/README +76 -0
  77. data/vendor/xss-shield/init.rb +16 -0
  78. data/vendor/xss-shield/lib/xss_shield.rb +6 -0
  79. data/vendor/xss-shield/lib/xss_shield/erb_hacks.rb +111 -0
  80. data/vendor/xss-shield/lib/xss_shield/haml_hacks.rb +42 -0
  81. data/vendor/xss-shield/lib/xss_shield/safe_string.rb +47 -0
  82. data/vendor/xss-shield/lib/xss_shield/secure_helpers.rb +40 -0
  83. data/vendor/xss-shield/test/test_actionview_integration.rb +40 -0
  84. data/vendor/xss-shield/test/test_erb.rb +44 -0
  85. data/vendor/xss-shield/test/test_haml.rb +43 -0
  86. data/vendor/xss-shield/test/test_helpers.rb +25 -0
  87. data/vendor/xss-shield/test/test_safe_string.rb +55 -0
  88. metadata +247 -0
@@ -0,0 +1,14 @@
1
+ # dynamically mixed in to response objects
2
+ module Relevance
3
+ module CoreExtensions
4
+ module Response
5
+ def html?
6
+ # some versions of Rails integration tests don't set content type
7
+ # so we are treating nil as html. A better fix would be welcome here.
8
+ (content_type.respond_to?(:html?) ?
9
+ content_type.html? : content_type =~ %r{^text/html}) ||
10
+ content_type.nil?
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ module Relevance
2
+ module CoreExtensions
3
+
4
+ module TestCaseExtensions
5
+ def tarantula_crawl(integration_test, options = {})
6
+ url = options[:url] || "/"
7
+ t = tarantula_crawler(integration_test, options)
8
+ t.crawl url
9
+ end
10
+
11
+ def tarantula_crawler(integration_test, options = {})
12
+ Relevance::Tarantula::RailsIntegrationProxy.rails_integration_test(integration_test, options)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+
19
+ if defined? ActionController::IntegrationTest
20
+ ActionController::IntegrationTest.class_eval { include Relevance::CoreExtensions::TestCaseExtensions }
21
+ end
@@ -0,0 +1,55 @@
1
+ require 'forwardable'
2
+ require 'erb'
3
+ require 'active_support'
4
+ require 'action_controller'
5
+ require 'htmlentities'
6
+
7
+ if RUBY_VERSION < '1.9.1'
8
+ warn "***************************************************"
9
+ warn "tarantula will stop supporting ruby 1.8.x in 0.6.0."
10
+ warn "***************************************************"
11
+ end
12
+
13
+ module Relevance; end
14
+ module Relevance; module CoreExtensions; end; end
15
+ module Relevance
16
+ module Tarantula
17
+ def tarantula_home
18
+ File.expand_path(File.join(File.dirname(__FILE__), "../.."))
19
+ end
20
+ def log(msg)
21
+ puts msg if verbose
22
+ end
23
+ def rails_root
24
+ ::Rails.root.to_s
25
+ end
26
+ def verbose
27
+ ENV["VERBOSE"]
28
+ end
29
+ end
30
+ end
31
+
32
+ require "relevance/core_extensions/test_case"
33
+ require "relevance/core_extensions/ellipsize"
34
+ require "relevance/core_extensions/file"
35
+ require "relevance/core_extensions/response"
36
+ require "relevance/core_extensions/metaclass"
37
+
38
+ require "relevance/tarantula/html_reporter"
39
+ require "relevance/tarantula/html_report_helper"
40
+ require "relevance/tarantula/io_reporter"
41
+ require "relevance/tarantula/recording"
42
+ require "relevance/tarantula/response"
43
+ require "relevance/tarantula/result"
44
+ require "relevance/tarantula/log_grabber"
45
+ require "relevance/tarantula/invalid_html_handler"
46
+ require "relevance/tarantula/transform"
47
+ require "relevance/tarantula/crawler"
48
+ require "relevance/tarantula/basic_attack"
49
+ require "relevance/tarantula/form"
50
+ require "relevance/tarantula/form_submission"
51
+ require "relevance/tarantula/attack"
52
+ require "relevance/tarantula/attack_handler"
53
+ require "relevance/tarantula/link"
54
+
55
+ require "relevance/tarantula/tidy_handler" if ENV['TIDY_PATH']
@@ -0,0 +1,22 @@
1
+ module Relevance
2
+ module Tarantula
3
+
4
+ class Attack
5
+ HASHABLE_ATTRS = [:name, :input, :output, :description]
6
+ attr_accessor *HASHABLE_ATTRS
7
+ def initialize(hash)
8
+ hash.each do |k,v|
9
+ raise ArgumentError, k unless HASHABLE_ATTRS.member?(k)
10
+ self.instance_variable_set("@#{k}", v)
11
+ end
12
+ end
13
+ def ==(other)
14
+ Relevance::Tarantula::Attack === other && HASHABLE_ATTRS.all? { |attr| send(attr) == other.send(attr)}
15
+ end
16
+ def input(input_field=nil)
17
+ @input
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ require 'hpricot'
2
+
3
+ module Relevance
4
+ module Tarantula
5
+
6
+ class AttackHandler
7
+ include ERB::Util
8
+
9
+ def attacks
10
+ Relevance::Tarantula::FormSubmission.attacks.select(&:output)
11
+ end
12
+
13
+ def handle(result)
14
+ return unless attacks.size > 0
15
+ regexp = '(' + attacks.map {|a| Regexp.escape a.output}.join('|') + ')'
16
+ response = result.response
17
+ return unless response.html?
18
+ if n = (response.body =~ /#{regexp}/)
19
+ error_result = result.dup
20
+ error_result.success = false
21
+ error_result.description = "XSS error found, match was: #{h($1)}"
22
+ error_result.data = <<-STR
23
+ ########################################################################
24
+ # Text around unescaped string: #{$1}
25
+ ########################################################################
26
+ #{response.body[[0, n - 200].max , 400]}
27
+
28
+
29
+
30
+
31
+
32
+ ########################################################################
33
+ # Attack information:
34
+ ########################################################################
35
+ #{attacks.select {|a| a.output == $1}[0].to_yaml}
36
+ STR
37
+ error_result
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ module Relevance
2
+ module Tarantula
3
+
4
+ class BasicAttack
5
+ ATTRS = [:name, :output, :description]
6
+
7
+ attr_reader *ATTRS
8
+
9
+ def initialize
10
+ @name = "Tarantula Basic Fuzzer"
11
+ @output = nil
12
+ @description = "Supplies purely random but simplistically generated form input."
13
+ end
14
+
15
+ def ==(other)
16
+ Relevance::Tarantula::BasicAttack === other && ATTRS.all? { |attr| send(attr) == other.send(attr)}
17
+ end
18
+
19
+ def input(input_field)
20
+ case input_field['name']
21
+ when /amount/ then random_int
22
+ when /_id$/ then random_whole_number
23
+ when /uploaded_data/ then nil
24
+ when nil then input['value']
25
+ else
26
+ random_int
27
+ end
28
+ end
29
+
30
+ def big_number
31
+ 10000 # arbitrary
32
+ end
33
+
34
+ def random_int
35
+ rand(big_number) - (big_number/2)
36
+ end
37
+
38
+ def random_whole_number
39
+ rand(big_number)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,271 @@
1
+ require 'active_record'
2
+ require 'active_record/base'
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "rails_integration_proxy"))
4
+ require File.expand_path(File.join(File.dirname(__FILE__), "html_document_handler.rb"))
5
+
6
+ module Relevance
7
+ module Tarantula
8
+
9
+ class Crawler
10
+ extend Forwardable
11
+ include Relevance::Tarantula
12
+
13
+ class CrawlTimeout < RuntimeError; end
14
+
15
+ attr_accessor :proxy, :handlers, :skip_uri_patterns, :log_grabber,
16
+ :reporters, :crawl_queue, :links_queued,
17
+ :form_signatures_queued, :max_url_length, :response_code_handler,
18
+ :times_to_crawl, :fuzzers, :test_name, :crawl_timeout
19
+ attr_reader :transform_url_patterns, :referrers, :failures, :successes, :crawl_start_times, :crawl_end_times
20
+
21
+ def initialize
22
+ @max_url_length = 1024
23
+ @successes = []
24
+ @failures = []
25
+ @handlers = [@response_code_handler = Result]
26
+ @links_queued = Set.new
27
+ @form_signatures_queued = Set.new
28
+ @crawl_queue = []
29
+ @crawl_start_times, @crawl_end_times = [], []
30
+ @crawl_timeout = 20.minutes
31
+ @referrers = {}
32
+ @skip_uri_patterns = [
33
+ /^javascript/,
34
+ /^mailto/,
35
+ /^http/,
36
+ ]
37
+ self.transform_url_patterns = [
38
+ [/#.*$/, '']
39
+ ]
40
+ @reporters = [Relevance::Tarantula::IOReporter.new($stderr)]
41
+ @decoder = HTMLEntities.new
42
+ @times_to_crawl = 1
43
+ @fuzzers = [Relevance::Tarantula::FormSubmission]
44
+
45
+ @stdout_tty = $stdout.tty?
46
+ end
47
+
48
+ def method_missing(meth, *args)
49
+ super unless Result::ALLOW_NNN_FOR =~ meth.to_s
50
+ @response_code_handler.send(meth, *args)
51
+ end
52
+
53
+ def transform_url_patterns=(patterns)
54
+ @transform_url_patterns = patterns.map do |pattern|
55
+ Array === pattern ? Relevance::Tarantula::Transform.new(*pattern) : pattern
56
+ end
57
+ end
58
+
59
+ def crawl(url = "/")
60
+ orig_links_queued = @links_queued.dup
61
+ orig_form_signatures_queued = @form_signatures_queued.dup
62
+ orig_crawl_queue = @crawl_queue.dup
63
+ @times_to_crawl.times do |num|
64
+ queue_link url
65
+
66
+ begin
67
+ do_crawl num
68
+ rescue CrawlTimeout => e
69
+ puts
70
+ puts e.message
71
+ end
72
+
73
+ puts "#{ActiveSupport::Inflector.ordinalize((num+1))} crawl" if @times_to_crawl > 1
74
+
75
+ if num + 1 < @times_to_crawl
76
+ @links_queued = orig_links_queued
77
+ @form_signatures_queued = orig_form_signatures_queued
78
+ @crawl_queue = orig_crawl_queue
79
+ @referrers = {}
80
+ end
81
+ end
82
+ rescue Interrupt
83
+ $stderr.puts "CTRL-C"
84
+ ensure
85
+ report_results
86
+ end
87
+
88
+ def finished?
89
+ @crawl_queue.empty?
90
+ end
91
+
92
+ def do_crawl(number)
93
+ while (!finished?)
94
+ @crawl_start_times << Time.now
95
+ crawl_the_queue(number)
96
+ @crawl_end_times << Time.now
97
+ end
98
+ end
99
+
100
+ def crawl_the_queue(number = 0)
101
+ while (request = @crawl_queue.shift)
102
+ request.crawl
103
+ blip(number)
104
+ end
105
+ end
106
+
107
+ def save_result(result)
108
+ reporters.each do |reporter|
109
+ reporter.report(result)
110
+ end
111
+ end
112
+
113
+ def handle_link_results(link, result)
114
+ handlers.each do |h|
115
+ begin
116
+ save_result h.handle(result)
117
+ rescue Exception => e
118
+ log "error handling #{link} #{e.message}"
119
+ # TODO: pass to results
120
+ end
121
+ end
122
+ end
123
+
124
+ def follow(method, url, data=nil)
125
+ proxy.send(method, url, data)
126
+ end
127
+
128
+ def submit(method, action, data)
129
+ proxy.send(method, action, data)
130
+ end
131
+
132
+ def elasped_time_for_pass(num)
133
+ Time.now - crawl_start_times[num]
134
+ end
135
+
136
+ def grab_log!
137
+ @log_grabber && @log_grabber.grab!
138
+ end
139
+
140
+ def make_result(options)
141
+ defaults = {
142
+ :log => grab_log!,
143
+ :test_name => test_name
144
+ }
145
+ Result.new(defaults.merge(options)).freeze
146
+ end
147
+
148
+ def handle_form_results(form, response)
149
+ handlers.each do |h|
150
+ save_result h.handle(Result.new(:method => form.meth,
151
+ :url => form.action,
152
+ :response => response,
153
+ :log => grab_log!,
154
+ :referrer => form.action,
155
+ :data => form.data.inspect,
156
+ :test_name => test_name).freeze)
157
+ end
158
+ end
159
+
160
+ def should_skip_url?(url)
161
+ return true if url.blank?
162
+ if @skip_uri_patterns.any? {|pattern| pattern =~ url}
163
+ log "Skipping #{url}"
164
+ return true
165
+ end
166
+ if url.length > max_url_length
167
+ log "Skipping long url #{url}"
168
+ return true
169
+ end
170
+ end
171
+
172
+ def should_skip_link?(link)
173
+ should_skip_url?(link.href) || @links_queued.member?(link)
174
+ end
175
+
176
+ def should_skip_form_submission?(fs)
177
+ should_skip_url?(fs.action) || @form_signatures_queued.member?(fs.signature)
178
+ end
179
+
180
+ def transform_url(url)
181
+ return unless url
182
+ url = @decoder.decode(url)
183
+ @transform_url_patterns.each do |pattern|
184
+ url = pattern[url]
185
+ end
186
+ url
187
+ end
188
+
189
+ def queue_link(dest, referrer = nil)
190
+ dest = Link.new(dest, self, referrer)
191
+ return if should_skip_link?(dest)
192
+ append_to_queue(dest)
193
+ @links_queued << dest
194
+ dest
195
+ end
196
+
197
+ def queue_form(form, referrer = nil)
198
+ fuzzers.each do |fuzzer|
199
+ fuzzer.mutate(Form.new(form, self, referrer)).each do |fs|
200
+ # fs = fuzzer.new(Form.new(form, self, referrer))
201
+ fs.action = transform_url(fs.action)
202
+ return if should_skip_form_submission?(fs)
203
+ @referrers[fs.action] = referrer if referrer
204
+ append_to_queue(fs)
205
+ @form_signatures_queued << fs.signature
206
+ end
207
+ end
208
+ end
209
+
210
+ # append delete requests to the end of the queue, all others just before the first delete request
211
+ def append_to_queue(request)
212
+ if request.meth != 'delete' && index = @crawl_queue.index {|r| r.meth == 'delete' }
213
+ @crawl_queue.insert(index, request)
214
+ elsif request.meth == 'delete' && parent_index = @crawl_queue.index {|r| r.meth == 'delete' && request.url.start_with?(r.url) }
215
+ @crawl_queue.insert(parent_index, request)
216
+ else
217
+ @crawl_queue << request
218
+ end
219
+ end
220
+
221
+ def report_dir
222
+ File.join(rails_root, "tmp", "tarantula")
223
+ end
224
+
225
+ def generate_reports
226
+ errors = []
227
+ reporters.each do |reporter|
228
+ begin
229
+ reporter.finish_report(test_name)
230
+ rescue RuntimeError => e
231
+ errors << e
232
+ end
233
+ end
234
+ unless errors.empty?
235
+ raise errors.map(&:message).join("\n")
236
+ end
237
+ end
238
+
239
+ def report_results
240
+ puts "Crawled #{total_links_count} links and forms."
241
+ generate_reports
242
+ end
243
+
244
+ def total_links_count
245
+ @links_queued.size + @form_signatures_queued.size
246
+ end
247
+
248
+ def links_remaining_count
249
+ @crawl_queue.size
250
+ end
251
+
252
+ def links_completed_count
253
+ total_links_count - links_remaining_count
254
+ end
255
+
256
+ def blip(number = 0)
257
+ unless verbose
258
+ print "\r #{links_completed_count} of #{total_links_count} links completed " if @stdout_tty
259
+ timeout_if_too_long(number)
260
+ end
261
+ end
262
+
263
+ def timeout_if_too_long(number = 0)
264
+ if elasped_time_for_pass(number) > crawl_timeout
265
+ raise CrawlTimeout, "Exceeded crawl timeout of #{crawl_timeout} seconds - skipping to the next crawl..."
266
+ end
267
+ end
268
+ end
269
+
270
+ end
271
+ end