arachni 1.0.4 → 1.0.5

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/README.md +8 -4
  4. data/bin/arachni_console +1 -1
  5. data/components/checks/active/no_sql_injection.rb +4 -4
  6. data/components/checks/passive/common_directories/directories.txt +1 -0
  7. data/components/checks/passive/common_files/filenames.txt +1 -0
  8. data/components/plugins/login_script.rb +156 -0
  9. data/components/reporters/plugin_formatters/html/login_script.rb +48 -0
  10. data/components/reporters/plugin_formatters/stdout/login_script.rb +23 -0
  11. data/components/reporters/plugin_formatters/xml/login_script.rb +26 -0
  12. data/components/reporters/xml/schema.xsd +17 -0
  13. data/lib/arachni/browser.rb +7 -4
  14. data/lib/arachni/browser/javascript.rb +40 -4
  15. data/lib/arachni/browser/javascript/proxy.rb +1 -1
  16. data/lib/arachni/browser_cluster/worker.rb +14 -4
  17. data/lib/arachni/check/auditor.rb +24 -7
  18. data/lib/arachni/check/manager.rb +6 -0
  19. data/lib/arachni/framework.rb +54 -6
  20. data/lib/arachni/http/client.rb +41 -23
  21. data/lib/arachni/http/headers.rb +5 -1
  22. data/lib/arachni/http/message.rb +0 -7
  23. data/lib/arachni/http/request.rb +40 -32
  24. data/lib/arachni/http/response.rb +8 -1
  25. data/lib/arachni/platform/manager.rb +7 -0
  26. data/lib/arachni/rpc/server/framework/multi_instance.rb +1 -1
  27. data/lib/arachni/session.rb +88 -58
  28. data/lib/arachni/state/framework.rb +34 -5
  29. data/lib/arachni/support/profiler.rb +2 -0
  30. data/lib/arachni/uri.rb +2 -1
  31. data/lib/version +1 -1
  32. data/spec/arachni/browser/javascript_spec.rb +15 -0
  33. data/spec/arachni/check/manager_spec.rb +17 -0
  34. data/spec/arachni/framework_spec.rb +4 -2
  35. data/spec/arachni/http/client_spec.rb +1 -1
  36. data/spec/arachni/session_spec.rb +80 -37
  37. data/spec/arachni/state/framework_spec.rb +34 -1
  38. data/spec/arachni/uri_spec.rb +7 -0
  39. data/spec/components/plugins/login_script_spec.rb +157 -0
  40. data/spec/support/servers/plugins/login_script.rb +13 -0
  41. data/ui/cli/output.rb +26 -9
  42. metadata +11 -3
@@ -147,6 +147,8 @@ class Profiler
147
147
  private
148
148
 
149
149
  def object_within_namespace?( object, namespaces )
150
+ return true if namespaces.empty?
151
+
150
152
  namespaces.each do |namespace|
151
153
  return true if object.class.to_s.start_with?( namespace.to_s )
152
154
  end
@@ -563,6 +563,7 @@ class URI
563
563
  # @return [String]
564
564
  # `domain_name.tld`
565
565
  def domain
566
+ return if !host
566
567
  return host if ip_address?
567
568
 
568
569
  s = host.split( '.' )
@@ -612,7 +613,7 @@ class URI
612
613
  q = self.query
613
614
  return {} if q.to_s.empty?
614
615
 
615
- q.split( '&' ).inject( {} ) do |h, pair|
616
+ q.recode.split( '&' ).inject( {} ) do |h, pair|
616
617
  name, value = pair.split( '=', 2 )
617
618
  h[::URI.decode( name.to_s )] = ::URI.decode( value.to_s )
618
619
  h
@@ -1 +1 @@
1
- 1.0.4
1
+ 1.0.5
@@ -372,4 +372,19 @@ _#{subject.token}TaintTracer.update_tracers(); // Injected by Arachni::Browser::
372
372
  end
373
373
  end
374
374
 
375
+ describe '#run_without_elements' do
376
+ it 'executes the given script and unwraps Watir elements' do
377
+ @browser.load @dom_monitor_url
378
+ source = Nokogiri::HTML(@browser.source).to_s
379
+
380
+ source.should ==
381
+ Nokogiri::HTML(subject.run_without_elements( 'return document.documentElement' ) ).to_s
382
+
383
+ source.should ==
384
+ Nokogiri::HTML(subject.run_without_elements( 'return [document.documentElement]' ).first ).to_s
385
+
386
+ source.should ==
387
+ Nokogiri::HTML(subject.run_without_elements( 'return { html: document.documentElement }' )['html'] ).to_s
388
+ end
389
+ end
375
390
  end
@@ -95,6 +95,23 @@ describe Arachni::Check::Manager do
95
95
  issues.size.should equal 1
96
96
  issues.first.name.should == checks['test'].info[:issue][:name]
97
97
  end
98
+
99
+ context 'when the check was ran' do
100
+ it 'returns true' do
101
+ checks.load :test
102
+ checks.run_one( checks.values.first, page ).should be_true
103
+ end
104
+ end
105
+
106
+ context 'when the check was not ran' do
107
+ it 'returns false' do
108
+ checks.load :test
109
+
110
+ allow(Arachni::Checks::Test).to receive(:check?).and_return(false)
111
+
112
+ checks.run_one( checks.values.first, page ).should be_false
113
+ end
114
+ end
98
115
  end
99
116
 
100
117
  end
@@ -1100,6 +1100,10 @@ describe Arachni::Framework do
1100
1100
  end
1101
1101
  end
1102
1102
 
1103
+ context 'when the page contains elements seen in previous pages' do
1104
+ it 'removes them from the page'
1105
+ end
1106
+
1103
1107
  context 'when a check fails with an exception' do
1104
1108
  it 'moves to the next one' do
1105
1109
  @options.paths.checks = fixtures_path + '/checks/'
@@ -1208,8 +1212,6 @@ describe Arachni::Framework do
1208
1212
  let(:page) { Arachni::Page.from_url( @url + '/train/true' ) }
1209
1213
 
1210
1214
  it 'pushes it to the page audit queue and returns true' do
1211
- page = Arachni::Page.from_url( @url + '/train/true' )
1212
-
1213
1215
  subject.options.audit.elements :links, :forms, :cookies
1214
1216
  subject.checks.load :taint
1215
1217
 
@@ -497,7 +497,7 @@ describe Arachni::HTTP::Client do
497
497
 
498
498
  context "when a #{RuntimeError} occurs" do
499
499
  it 'returns nil' do
500
- subject.instance.stub(:hydra_run){ raise }
500
+ subject.instance.stub(:client_run){ raise }
501
501
 
502
502
  subject.run.should be_nil
503
503
  end
@@ -108,56 +108,99 @@ describe Arachni::Session do
108
108
  end
109
109
 
110
110
  describe '#login' do
111
- it 'finds and submits the login form with the given credentials' do
112
- configured.login
113
- configured.should be_logged_in
114
- end
111
+ context 'when given a login sequence' do
112
+ context 'when a browser is available' do
113
+ it 'passes a browser instance' do
114
+ b = nil
115
+ subject.record_login_sequence do |browser|
116
+ b = browser
117
+ end
115
118
 
116
- context 'when a browser is available' do
117
- before { subject.stub(:has_browser?) { false } }
119
+ subject.login
118
120
 
119
- it 'uses the framework Page helpers' do
120
- configured.should_not be_logged_in
121
- configured.login.should be_kind_of Arachni::Page
122
- configured.should be_logged_in
121
+ b.should be_kind_of Arachni::Browser
122
+ end
123
+
124
+ it 'updates the system cookies from the browser' do
125
+ subject.record_login_sequence do |browser|
126
+ browser.goto @url
127
+ browser.watir.cookies.add 'foo', 'bar'
128
+ end
129
+
130
+ subject.login
131
+
132
+ Arachni::HTTP::Client.cookies.find { |c| c.name == 'foo' }.should be_true
133
+ end
123
134
  end
124
- end
125
135
 
126
- context 'when a browser is available' do
127
- it 'can handle Javascript forms' do
128
- subject.configure(
129
- url: "#{@url}/javascript_login",
130
- inputs: {
131
- username: 'john',
132
- password: 'doe'
133
- }
134
- )
136
+ context 'when a browser is not available' do
137
+ before { subject.stub(:has_browser?) { false } }
135
138
 
136
- @opts.session.check_url = @url
137
- @opts.session.check_pattern = 'logged-in user'
139
+ it 'does not pass a browser instance' do
140
+ b = true
141
+ subject.record_login_sequence do |browser|
142
+ b = browser
143
+ end
138
144
 
139
- subject.login
145
+ subject.login
140
146
 
141
- subject.should be_logged_in
147
+ b.should be_nil
148
+ end
149
+ end
150
+ end
151
+
152
+ context 'when given login form info' do
153
+ it 'finds and submits the login form with the given credentials' do
154
+ configured.login
155
+ configured.should be_logged_in
156
+ end
157
+
158
+ context 'when a browser is not available' do
159
+ before { subject.stub(:has_browser?) { false } }
160
+
161
+ it 'uses the framework Page helpers' do
162
+ configured.should_not be_logged_in
163
+ configured.login.should be_kind_of Arachni::Page
164
+ configured.should be_logged_in
165
+ end
142
166
  end
143
167
 
144
- it 'returns the resulting browser evaluated page' do
145
- configured.login.should be_kind_of Arachni::Page
168
+ context 'when a browser is available' do
169
+ it 'can handle Javascript forms' do
170
+ subject.configure(
171
+ url: "#{@url}/javascript_login",
172
+ inputs: {
173
+ username: 'john',
174
+ password: 'doe'
175
+ }
176
+ )
146
177
 
147
- transition = configured.login.dom.transitions.first
148
- transition.event.should == :load
149
- transition.element.should == :page
150
- transition.options[:url].should == configured.configuration[:url]
178
+ @opts.session.check_url = @url
179
+ @opts.session.check_pattern = 'logged-in user'
151
180
 
152
- transition = configured.login.dom.transitions.last
153
- transition.event.should == :submit
154
- transition.element.tag_name.should == :form
181
+ subject.login
182
+
183
+ subject.should be_logged_in
184
+ end
155
185
 
156
- transition.options[:inputs]['username'].should ==
157
- configured.configuration[:inputs][:username]
186
+ it 'returns the resulting browser evaluated page' do
187
+ configured.login.should be_kind_of Arachni::Page
158
188
 
159
- transition.options[:inputs]['password'].should ==
160
- configured.configuration[:inputs][:password]
189
+ transition = configured.login.dom.transitions.first
190
+ transition.event.should == :load
191
+ transition.element.should == :page
192
+ transition.options[:url].should == configured.configuration[:url]
193
+
194
+ transition = configured.login.dom.transitions.last
195
+ transition.event.should == :submit
196
+ transition.element.tag_name.should == :form
197
+
198
+ transition.options[:inputs]['username'].should ==
199
+ configured.configuration[:inputs][:username]
200
+
201
+ transition.options[:inputs]['password'].should ==
202
+ configured.configuration[:inputs][:password]
203
+ end
161
204
  end
162
205
  end
163
206
 
@@ -9,6 +9,7 @@ describe Arachni::State::Framework do
9
9
  before(:each) { subject.clear }
10
10
 
11
11
  let(:page) { Factory[:page] }
12
+ let(:element) { Factory[:link] }
12
13
  let(:url) { page.url }
13
14
  let(:dump_directory) do
14
15
  @dump_directory = "#{Dir.tmpdir}/framework-#{Arachni::Utilities.generate_token}"
@@ -114,6 +115,28 @@ describe Arachni::State::Framework do
114
115
  end
115
116
  end
116
117
 
118
+ describe '#element_checked?' do
119
+ context 'when an element has already been checked' do
120
+ it 'returns true' do
121
+ subject.element_pre_check_filter << element
122
+ subject.element_checked?( element ).should be_true
123
+ end
124
+ end
125
+
126
+ context 'when an element has not been checked' do
127
+ it 'returns false' do
128
+ subject.element_checked?( element ).should be_false
129
+ end
130
+ end
131
+ end
132
+
133
+ describe '#element_checked' do
134
+ it 'marks an element as checked' do
135
+ subject.element_checked element
136
+ subject.element_checked?( element ).should be_true
137
+ end
138
+ end
139
+
117
140
  describe '#page_seen?' do
118
141
  context 'when a page has already been seen' do
119
142
  it 'returns true' do
@@ -805,6 +828,15 @@ describe Arachni::State::Framework do
805
828
  described_class.load( dump_directory ).rpc.should be_kind_of described_class::RPC
806
829
  end
807
830
 
831
+ it 'loads #element_pre_check_filter from disk' do
832
+ subject.element_pre_check_filter << element
833
+
834
+ subject.dump( dump_directory )
835
+
836
+ described_class.load( dump_directory ).element_pre_check_filter.
837
+ collection.should == Set.new([element.coverage_hash])
838
+ end
839
+
808
840
  it 'loads #page_queue_filter from disk' do
809
841
  subject.page_queue_filter << page
810
842
 
@@ -837,7 +869,8 @@ describe Arachni::State::Framework do
837
869
  end
838
870
 
839
871
  describe '#clear' do
840
- %w(rpc browser_skip_states page_queue_filter url_queue_filter).each do |method|
872
+ %w(rpc element_pre_check_filter browser_skip_states page_queue_filter
873
+ url_queue_filter).each do |method|
841
874
  it "clears ##{method}" do
842
875
  subject.send(method).should receive(:clear)
843
876
  subject.clear
@@ -455,6 +455,13 @@ describe Arachni::URI do
455
455
  url = 'http://deep.subdomain.test.com/'
456
456
  described_class.parse( url ).domain.should == 'subdomain.test.com'
457
457
  end
458
+
459
+ context 'when no host is available' do
460
+ it 'returns nil' do
461
+ url = '/stuff/'
462
+ described_class.parse( url ).domain.should be_nil
463
+ end
464
+ end
458
465
  end
459
466
 
460
467
  describe '#ip_address?' do
@@ -0,0 +1,157 @@
1
+ require 'spec_helper'
2
+
3
+ describe name_from_filename do
4
+ include_examples 'plugin'
5
+
6
+ before( :all ) do
7
+ options.url = url
8
+ end
9
+
10
+ before :each do
11
+ options.session.check_url = nil
12
+ options.session.check_pattern = nil
13
+
14
+ IO.write( script_path, script )
15
+
16
+ options.plugins[component_name] = { 'script' => script_path }
17
+ end
18
+
19
+ after(:each) { FileUtils.rm_f script_path }
20
+
21
+ let(:script) { '' }
22
+ let(:script_path) { "#{Dir.tmpdir}/login_script_#{Time.now.to_i}" }
23
+
24
+ context 'when a browser' do
25
+ let(:script) do
26
+ <<EOSCRIPT
27
+ framework.options.datastore.browser = browser
28
+ EOSCRIPT
29
+ end
30
+
31
+ context 'is available' do
32
+ it "exposes a Watir::Browser interface via the 'browser' variable" do
33
+ run
34
+
35
+ options.datastore.browser.should be_kind_of Watir::Browser
36
+ end
37
+ end
38
+
39
+ context 'is not available' do
40
+ it "sets 'browser' to 'nil'" do
41
+ framework.options.scope.dom_depth_limit = 0
42
+ run
43
+
44
+ options.datastore.browser.should be_nil
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'when the login was successful' do
50
+ before :each do
51
+ options.session.check_url = url
52
+ options.session.check_pattern = 'Hi there logged-in user'
53
+ end
54
+
55
+ let(:script) do
56
+ <<EOSCRIPT
57
+ http.cookie_jar.update 'success' => 'true'
58
+ EOSCRIPT
59
+ end
60
+
61
+ it 'sets the status' do
62
+ run
63
+
64
+ actual_results['status'].should == 'success'
65
+ end
66
+
67
+ it 'sets the message' do
68
+ run
69
+
70
+ actual_results['message'].should == plugin::STATUSES[:success]
71
+ end
72
+
73
+ it 'sets the cookies' do
74
+ run
75
+
76
+ actual_results['cookies'].should == { 'success' => 'true' }
77
+ end
78
+ end
79
+
80
+ context 'when there is no session check' do
81
+ let(:script) do
82
+ <<EOSCRIPT
83
+ http.cookie_jar.update 'success' => 'true'
84
+ EOSCRIPT
85
+ end
86
+
87
+ it 'sets the status' do
88
+ run
89
+
90
+ actual_results['status'].should == 'missing_check'
91
+ end
92
+
93
+ it 'sets the message' do
94
+ run
95
+
96
+ actual_results['message'].should == plugin::STATUSES[:missing_check]
97
+ end
98
+
99
+ it 'aborts the scan' do
100
+ run
101
+
102
+ framework.status.should == :aborted
103
+ end
104
+ end
105
+
106
+ context 'when the session check fails' do
107
+ before :each do
108
+ options.session.check_url = url
109
+ options.session.check_pattern = 'Hi there logged-in user'
110
+ end
111
+
112
+ it 'sets the status' do
113
+ run
114
+
115
+ actual_results['status'].should == 'failure'
116
+ end
117
+
118
+ it 'sets the message' do
119
+ run
120
+
121
+ actual_results['message'].should == plugin::STATUSES[:failure]
122
+ end
123
+
124
+ it 'aborts the scan' do
125
+ run
126
+
127
+ framework.status.should == :aborted
128
+ end
129
+ end
130
+
131
+ context 'when there is a runtime error in the script' do
132
+ let(:script) do
133
+ <<EOSCRIPT
134
+ fail
135
+ EOSCRIPT
136
+ end
137
+
138
+ it 'sets the status' do
139
+ run
140
+
141
+ actual_results['status'].should == 'error'
142
+ end
143
+
144
+ it 'sets the message' do
145
+ run
146
+
147
+ actual_results['message'].should == plugin::STATUSES[:error]
148
+ end
149
+
150
+ it 'aborts the scan' do
151
+ run
152
+
153
+ framework.status.should == :aborted
154
+ end
155
+ end
156
+
157
+ end