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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +8 -4
- data/bin/arachni_console +1 -1
- data/components/checks/active/no_sql_injection.rb +4 -4
- data/components/checks/passive/common_directories/directories.txt +1 -0
- data/components/checks/passive/common_files/filenames.txt +1 -0
- data/components/plugins/login_script.rb +156 -0
- data/components/reporters/plugin_formatters/html/login_script.rb +48 -0
- data/components/reporters/plugin_formatters/stdout/login_script.rb +23 -0
- data/components/reporters/plugin_formatters/xml/login_script.rb +26 -0
- data/components/reporters/xml/schema.xsd +17 -0
- data/lib/arachni/browser.rb +7 -4
- data/lib/arachni/browser/javascript.rb +40 -4
- data/lib/arachni/browser/javascript/proxy.rb +1 -1
- data/lib/arachni/browser_cluster/worker.rb +14 -4
- data/lib/arachni/check/auditor.rb +24 -7
- data/lib/arachni/check/manager.rb +6 -0
- data/lib/arachni/framework.rb +54 -6
- data/lib/arachni/http/client.rb +41 -23
- data/lib/arachni/http/headers.rb +5 -1
- data/lib/arachni/http/message.rb +0 -7
- data/lib/arachni/http/request.rb +40 -32
- data/lib/arachni/http/response.rb +8 -1
- data/lib/arachni/platform/manager.rb +7 -0
- data/lib/arachni/rpc/server/framework/multi_instance.rb +1 -1
- data/lib/arachni/session.rb +88 -58
- data/lib/arachni/state/framework.rb +34 -5
- data/lib/arachni/support/profiler.rb +2 -0
- data/lib/arachni/uri.rb +2 -1
- data/lib/version +1 -1
- data/spec/arachni/browser/javascript_spec.rb +15 -0
- data/spec/arachni/check/manager_spec.rb +17 -0
- data/spec/arachni/framework_spec.rb +4 -2
- data/spec/arachni/http/client_spec.rb +1 -1
- data/spec/arachni/session_spec.rb +80 -37
- data/spec/arachni/state/framework_spec.rb +34 -1
- data/spec/arachni/uri_spec.rb +7 -0
- data/spec/components/plugins/login_script_spec.rb +157 -0
- data/spec/support/servers/plugins/login_script.rb +13 -0
- data/ui/cli/output.rb +26 -9
- metadata +11 -3
data/lib/arachni/uri.rb
CHANGED
@@ -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
|
data/lib/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
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
|
|
@@ -108,56 +108,99 @@ describe Arachni::Session do
|
|
108
108
|
end
|
109
109
|
|
110
110
|
describe '#login' do
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
117
|
-
before { subject.stub(:has_browser?) { false } }
|
119
|
+
subject.login
|
118
120
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
127
|
-
|
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
|
-
|
137
|
-
|
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
|
-
|
145
|
+
subject.login
|
140
146
|
|
141
|
-
|
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
|
-
|
145
|
-
|
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
|
-
|
148
|
-
|
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
|
-
|
153
|
-
|
154
|
-
|
181
|
+
subject.login
|
182
|
+
|
183
|
+
subject.should be_logged_in
|
184
|
+
end
|
155
185
|
|
156
|
-
|
157
|
-
configured.
|
186
|
+
it 'returns the resulting browser evaluated page' do
|
187
|
+
configured.login.should be_kind_of Arachni::Page
|
158
188
|
|
159
|
-
|
160
|
-
|
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
|
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
|
data/spec/arachni/uri_spec.rb
CHANGED
@@ -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
|