arachni 0.2.2.1 → 0.2.2.2
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.
- data/CHANGELOG.md +30 -0
- data/CONTRIBUTORS.md +1 -0
- data/README.md +28 -8
- data/Rakefile +1 -0
- data/bin/arachni_web_autostart +46 -0
- data/lib/anemone/page.rb +1 -0
- data/lib/arachni.rb +1 -1
- data/lib/framework.rb +8 -3
- data/lib/http.rb +9 -39
- data/lib/mixins/observable.rb +87 -0
- data/lib/module/auditor.rb +14 -0
- data/lib/module/base.rb +0 -14
- data/lib/nokogiri/xml/node.rb +42 -0
- data/lib/ui/cli/cli.rb +1 -1
- data/lib/ui/web/log.rb +21 -14
- data/lib/ui/web/report_manager.rb +100 -15
- data/lib/ui/web/server.rb +24 -33
- data/lib/ui/web/server/public/reports/demo.testfire.net:Sun Mar 20 02:48:10 2011.afr +104829 -0
- data/lib/ui/web/server/views/layout.erb +1 -1
- data/lib/ui/web/server/views/options.erb +10 -2
- data/lib/ui/web/server/views/plugins.erb +1 -1
- data/lib/ui/web/server/views/reports.erb +8 -4
- data/lib/ui/xmlrpc/xmlrpc.rb +1 -1
- data/metamodules/autothrottle.rb +2 -2
- data/metamodules/timeout_notice.rb +1 -1
- data/modules/audit/sqli_blind_rdiff.rb +1 -1
- data/modules/recon/common_files/filenames.txt +2 -0
- data/modules/recon/directory_listing.rb +1 -0
- data/modules/recon/interesting_responses.rb +3 -3
- data/path_extractors/generic.rb +5 -1
- data/plugins/autologin.rb +15 -4
- data/plugins/content_types.rb +2 -2
- data/plugins/cookie_collector.rb +9 -16
- data/plugins/profiler.rb +237 -0
- data/reports/html.rb +21 -6
- data/reports/html/default.erb +4 -2
- data/reports/plugin_formatters/html/autologin.rb +63 -0
- data/reports/plugin_formatters/html/profiler.rb +71 -0
- data/reports/plugin_formatters/html/profiler/template.erb +177 -0
- data/reports/plugin_formatters/stdout/autologin.rb +55 -0
- data/reports/plugin_formatters/stdout/profiler.rb +90 -0
- data/reports/plugin_formatters/xml/autologin.rb +68 -0
- data/reports/plugin_formatters/xml/profiler.rb +120 -0
- metadata +23 -68
@@ -10,9 +10,17 @@
|
|
10
10
|
<% if opt['type'] == 'path' %>
|
11
11
|
disabled="disabled" type="file" value="<%=opt['default']%>"
|
12
12
|
<% elsif opt['type'] == 'bool' %>
|
13
|
-
type="checkbox"
|
13
|
+
type="checkbox"
|
14
|
+
<% if session_options[component['plug_name']] && !(val = session_options[component['plug_name']][opt['name']]).nil? %>
|
15
|
+
|
16
|
+
<%=val ? 'checked="checked"' : '' %>
|
17
|
+
|
18
|
+
<% elsif opt['default'] %>
|
19
|
+
checked="checked"
|
20
|
+
<%end%>
|
21
|
+
|
14
22
|
<% else %>
|
15
|
-
value="<%=opt['default']%>"
|
23
|
+
value="<%=( session_options[component['plug_name']] && (val = session_options[component['plug_name']][opt['name']]) ) ? val : opt['default']%>"
|
16
24
|
<%end%>
|
17
25
|
|
18
26
|
/>
|
@@ -30,7 +30,7 @@
|
|
30
30
|
<h5>Description</h5>
|
31
31
|
<pre class="notice"> <%=prep_description( escape( plugin['description'] ) )%></pre>
|
32
32
|
|
33
|
-
<%= erb :options, { :layout => false }, :component => plugin%>
|
33
|
+
<%= erb :options, { :layout => false }, :component => plugin, :session_options => session_options%>
|
34
34
|
|
35
35
|
<p>
|
36
36
|
<strong>Version:</strong> <%=plugin['version']%><br/>
|
@@ -22,7 +22,9 @@
|
|
22
22
|
<% if !reports.empty? %>
|
23
23
|
<table>
|
24
24
|
<tr>
|
25
|
+
<th>ID</th>
|
25
26
|
<th>Host</th>
|
27
|
+
<th>Issue count</th>
|
26
28
|
<th>Audit date</th>
|
27
29
|
<th>Report formats</th>
|
28
30
|
</tr>
|
@@ -30,19 +32,21 @@
|
|
30
32
|
<tr>
|
31
33
|
|
32
34
|
|
33
|
-
<td><%=report
|
34
|
-
<td><%=report
|
35
|
+
<td><%=report.id%></td>
|
36
|
+
<td><%=report.host%></td>
|
37
|
+
<td><%=report.issue_count%></td>
|
38
|
+
<td><%=report.datestamp%></td>
|
35
39
|
|
36
40
|
<td>
|
37
41
|
<ul class="reports">
|
38
42
|
<% available.each do |avail| %>
|
39
|
-
<li><a href="/report/<%=
|
43
|
+
<li><a href="/report/<%=report.id%>.<%=avail['rep_name']%>"><%=escape(avail['name'])%></a></li>
|
40
44
|
<%end%>
|
41
45
|
</ul>
|
42
46
|
</td>
|
43
47
|
|
44
48
|
<td>
|
45
|
-
<form action="/report/<%=
|
49
|
+
<form action="/report/<%=report.id%>/delete" method="post">
|
46
50
|
<%= csrf_tag %>
|
47
51
|
<input type="submit" value="Delete" />
|
48
52
|
</form>
|
data/lib/ui/xmlrpc/xmlrpc.rb
CHANGED
@@ -642,7 +642,7 @@ class XMLRPC
|
|
642
642
|
<zapotek@segfault.gr>
|
643
643
|
(With the support of the community and the Arachni Team.)
|
644
644
|
|
645
|
-
Website: http://github.com/Zapotek/arachni
|
645
|
+
Website: http://arachni.segfault.gr - http://github.com/Zapotek/arachni
|
646
646
|
Documentation: http://github.com/Zapotek/arachni/wiki'
|
647
647
|
print_line
|
648
648
|
print_line
|
data/metamodules/autothrottle.rb
CHANGED
@@ -17,7 +17,7 @@ module MetaModules
|
|
17
17
|
# @author: Tasos "Zapotek" Laskos
|
18
18
|
# <tasos.laskos@gmail.com>
|
19
19
|
# <zapotek@segfault.gr>
|
20
|
-
# @version: 0.1
|
20
|
+
# @version: 0.1.1
|
21
21
|
#
|
22
22
|
class AutoThrottle < Base
|
23
23
|
|
@@ -40,7 +40,7 @@ class AutoThrottle < Base
|
|
40
40
|
def prepare
|
41
41
|
|
42
42
|
# run for each response as it arrives
|
43
|
-
@http.
|
43
|
+
@http.add_on_complete {
|
44
44
|
|
45
45
|
# adjust only after finished bursts
|
46
46
|
next if @http.curr_res_cnt == 0 || @http.curr_res_cnt % @http.max_concurrency != 0
|
@@ -250,7 +250,7 @@ class BlindrDiffSQLInjection < Arachni::Module::Base
|
|
250
250
|
Issue::Element::LINK
|
251
251
|
],
|
252
252
|
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com> ',
|
253
|
-
:version => '0.
|
253
|
+
:version => '0.3',
|
254
254
|
:references => {
|
255
255
|
'OWASP' => 'http://www.owasp.org/index.php/Blind_SQL_Injection',
|
256
256
|
'MITRE - CAPEC' => 'http://capec.mitre.org/data/definitions/7.html'
|
@@ -20,7 +20,7 @@ module Modules
|
|
20
20
|
# @author: Tasos "Zapotek" Laskos
|
21
21
|
# <tasos.laskos@gmail.com>
|
22
22
|
# <zapotek@segfault.gr>
|
23
|
-
# @version: 0.1
|
23
|
+
# @version: 0.1.1
|
24
24
|
#
|
25
25
|
#
|
26
26
|
class InterestingResponses < Arachni::Module::Base
|
@@ -45,7 +45,7 @@ class InterestingResponses < Arachni::Module::Base
|
|
45
45
|
print_status( "Listening..." )
|
46
46
|
|
47
47
|
# tell the HTTP interface to cal this block every-time a request completes
|
48
|
-
@http.
|
48
|
+
@http.add_on_complete {
|
49
49
|
|res|
|
50
50
|
__log_results( res ) if !IGNORE_CODES.include?( res.code ) && !res.body.empty?
|
51
51
|
}
|
@@ -62,7 +62,7 @@ class InterestingResponses < Arachni::Module::Base
|
|
62
62
|
:description => %q{Logs all non 200 (OK) server responses.},
|
63
63
|
:elements => [ ],
|
64
64
|
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
|
65
|
-
:version => '0.1',
|
65
|
+
:version => '0.1.1',
|
66
66
|
:targets => { 'Generic' => 'all' },
|
67
67
|
:issue => {
|
68
68
|
:name => %q{Interesting server response.},
|
data/path_extractors/generic.rb
CHANGED
data/plugins/autologin.rb
CHANGED
@@ -27,6 +27,10 @@ class AutoLogin < Arachni::Plugin::Base
|
|
27
27
|
|
28
28
|
attr_accessor :http
|
29
29
|
|
30
|
+
MSG_SUCCESS = 'Form submitted successfully.'
|
31
|
+
MSG_FAILURE = 'Could not find a form suiting the provided params at: '
|
32
|
+
MSG_NO_RESPONSE = 'Form submitted but no response was returned.'
|
33
|
+
|
30
34
|
#
|
31
35
|
# @param [Arachni::Framework] framework
|
32
36
|
# @param [Hash] options options passed to the plugin
|
@@ -64,8 +68,8 @@ class AutoLogin < Arachni::Plugin::Base
|
|
64
68
|
}
|
65
69
|
|
66
70
|
if !login_form
|
67
|
-
|
68
|
-
@options['url'] )
|
71
|
+
register_results( { :code => 0, :msg => MSG_FAILURE + @options['url'] } )
|
72
|
+
print_error( MSG_FAILURE + @options['url'] )
|
69
73
|
return
|
70
74
|
end
|
71
75
|
|
@@ -80,10 +84,17 @@ class AutoLogin < Arachni::Plugin::Base
|
|
80
84
|
res = login_form.submit( :async => false ).response
|
81
85
|
|
82
86
|
if !res
|
83
|
-
|
87
|
+
register_results( { :code => -1, :msg => MSG_NO_RESPONSE } )
|
88
|
+
print_error( MSG_NO_RESPONSE )
|
84
89
|
return
|
85
90
|
else
|
86
|
-
|
91
|
+
register_results( { :code => 1, :msg => MSG_SUCCESS, :cookies => @http.current_cookies.dup } )
|
92
|
+
print_ok( MSG_SUCCESS )
|
93
|
+
print_info( 'Cookies set to:' )
|
94
|
+
@http.current_cookies.each_pair {
|
95
|
+
|name, val|
|
96
|
+
print_info( ' * ' + name + ' = ' + val )
|
97
|
+
}
|
87
98
|
end
|
88
99
|
|
89
100
|
end
|
data/plugins/content_types.rb
CHANGED
@@ -17,7 +17,7 @@ module Plugins
|
|
17
17
|
# @author: Tasos "Zapotek" Laskos
|
18
18
|
# <tasos.laskos@gmail.com>
|
19
19
|
# <zapotek@segfault.gr>
|
20
|
-
# @version: 0.1
|
20
|
+
# @version: 0.1.1
|
21
21
|
#
|
22
22
|
class ContentTypes < Arachni::Plugin::Base
|
23
23
|
|
@@ -38,7 +38,7 @@ class ContentTypes < Arachni::Plugin::Base
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def run( )
|
41
|
-
@framework.http.
|
41
|
+
@framework.http.add_on_complete {
|
42
42
|
|res|
|
43
43
|
|
44
44
|
next if @logged.include?( res.request.method.to_s.upcase + res.effective_url )
|
data/plugins/cookie_collector.rb
CHANGED
@@ -12,11 +12,12 @@ module Arachni
|
|
12
12
|
module Plugins
|
13
13
|
|
14
14
|
#
|
15
|
+
# Simple cookie collector
|
15
16
|
#
|
16
17
|
# @author: Tasos "Zapotek" Laskos
|
17
18
|
# <tasos.laskos@gmail.com>
|
18
19
|
# <zapotek@segfault.gr>
|
19
|
-
# @version: 0.1
|
20
|
+
# @version: 0.1.2
|
20
21
|
#
|
21
22
|
class CookieCollector < Arachni::Plugin::Base
|
22
23
|
|
@@ -33,18 +34,21 @@ class CookieCollector < Arachni::Plugin::Base
|
|
33
34
|
end
|
34
35
|
|
35
36
|
def run( )
|
36
|
-
@framework.http.
|
37
|
-
|res|
|
38
|
-
update(
|
37
|
+
@framework.http.add_on_new_cookies {
|
38
|
+
|cookies, res|
|
39
|
+
update( cookies, res )
|
39
40
|
}
|
40
41
|
end
|
41
42
|
|
42
43
|
def update( cookies, res )
|
43
44
|
return if cookies.empty? || !update?( cookies )
|
44
45
|
|
46
|
+
res_hash = res.to_hash
|
47
|
+
res_hash.delete( 'body' )
|
48
|
+
|
45
49
|
@cookies << {
|
46
50
|
:time => Time.now,
|
47
|
-
:res =>
|
51
|
+
:res => res_hash,
|
48
52
|
:cookies => cookies
|
49
53
|
}
|
50
54
|
end
|
@@ -60,17 +64,6 @@ class CookieCollector < Arachni::Plugin::Base
|
|
60
64
|
return false
|
61
65
|
end
|
62
66
|
|
63
|
-
def extract_cookies( res )
|
64
|
-
cookies = {}
|
65
|
-
|
66
|
-
Arachni::Parser.new( @framework.opts, res ).run.cookies.each {
|
67
|
-
|cookie|
|
68
|
-
cookies.merge!( cookie.simple )
|
69
|
-
}
|
70
|
-
|
71
|
-
return cookies
|
72
|
-
end
|
73
|
-
|
74
67
|
def clean_up
|
75
68
|
while( @framework.running? )
|
76
69
|
::IO.select( nil, nil, nil, 1 )
|
data/plugins/profiler.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
=begin
|
2
|
+
Arachni
|
3
|
+
Copyright (c) 2010-2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
4
|
+
|
5
|
+
This is free software; you can copy and distribute and modify
|
6
|
+
this program under the term of the GPL v2.0 License
|
7
|
+
(See LICENSE file for details)
|
8
|
+
|
9
|
+
=end
|
10
|
+
|
11
|
+
module Arachni
|
12
|
+
module Plugins
|
13
|
+
|
14
|
+
#
|
15
|
+
# Profiler plugin.
|
16
|
+
#
|
17
|
+
# Examines the behavior of the web application gathering general statistics
|
18
|
+
# and performs taint analysis to determine which inputs affect the output.
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# @author: Tasos "Zapotek" Laskos
|
22
|
+
# <tasos.laskos@gmail.com>
|
23
|
+
# <zapotek@segfault.gr>
|
24
|
+
# @version: 0.1
|
25
|
+
#
|
26
|
+
class Profiler < Arachni::Plugin::Base
|
27
|
+
|
28
|
+
#
|
29
|
+
# Assumes the identity of an Auditor.
|
30
|
+
#
|
31
|
+
# It will audit all inputs and log when inserted values appear in a page's body.<br/>
|
32
|
+
# It's does not perform any vulnerability assesment nor does it send attack payloads,
|
33
|
+
# just simple benign strings.
|
34
|
+
#
|
35
|
+
# Since an Auditor has formal specifications a plug-in can't directly become one
|
36
|
+
# due to it's abstract nature.
|
37
|
+
#
|
38
|
+
# Thus, we use this helper class to perform auditing duties.
|
39
|
+
#
|
40
|
+
# @author: Tasos "Zapotek" Laskos
|
41
|
+
# <tasos.laskos@gmail.com>
|
42
|
+
# <zapotek@segfault.gr>
|
43
|
+
# @version: 0.1
|
44
|
+
#
|
45
|
+
class Auditor < Arachni::Module::Base
|
46
|
+
|
47
|
+
attr_reader :http
|
48
|
+
attr_reader :page
|
49
|
+
|
50
|
+
def initialize( page )
|
51
|
+
super( page )
|
52
|
+
|
53
|
+
@id = Digest::SHA2.hexdigest( rand( 1000 ).to_s )
|
54
|
+
@opts = {
|
55
|
+
:format => [ Format::STRAIGHT ],
|
56
|
+
:elements => [
|
57
|
+
Issue::Element::FORM,
|
58
|
+
Issue::Element::LINK,
|
59
|
+
Issue::Element::COOKIE,
|
60
|
+
Issue::Element::HEADER
|
61
|
+
],
|
62
|
+
:remove_id => true
|
63
|
+
}
|
64
|
+
|
65
|
+
@@logged ||= Set.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def run( &logger )
|
69
|
+
audit( @id, @opts ) {
|
70
|
+
|res, opts, elem|
|
71
|
+
|
72
|
+
landed_elems = []
|
73
|
+
if res.body.substring?( @id )
|
74
|
+
landed_elems |= find_landing_elements( res )
|
75
|
+
end
|
76
|
+
|
77
|
+
if res.headers.to_s.substring?( @id )
|
78
|
+
landed_elems |= find_landing_header_fields( res )
|
79
|
+
end
|
80
|
+
|
81
|
+
if !landed_elems.empty?
|
82
|
+
|
83
|
+
@@logged << "#{elem.action}::#{elem.altered}::#{elem.type}"
|
84
|
+
|
85
|
+
logger.call( @id, res, elem, landed_elems )
|
86
|
+
end
|
87
|
+
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_landing_header_fields( res )
|
92
|
+
elems = []
|
93
|
+
|
94
|
+
parser = Arachni::Parser.new( Arachni::Options.instance, res )
|
95
|
+
parser.cookies.each {
|
96
|
+
|cookie|
|
97
|
+
elems << cookie if cookie.auditable.to_s.substring?( @id )
|
98
|
+
}
|
99
|
+
|
100
|
+
res.headers_hash.each_pair {
|
101
|
+
|k, v|
|
102
|
+
elems << Arachni::Parser::Element::Header.new( res.effective_url, { k => v } ) if v.substring?( @id )
|
103
|
+
}
|
104
|
+
|
105
|
+
return elems
|
106
|
+
end
|
107
|
+
|
108
|
+
def find_landing_elements( res )
|
109
|
+
elems = []
|
110
|
+
|
111
|
+
elems << Struct::Body.new( 'body', nil, { 'attrs' => {} } )
|
112
|
+
|
113
|
+
parser = Arachni::Parser.new( Arachni::Options.instance, res )
|
114
|
+
parser.forms.each {
|
115
|
+
|form|
|
116
|
+
elems << form if form.auditable.to_s.substring?( @id )
|
117
|
+
}
|
118
|
+
|
119
|
+
parser.links.each {
|
120
|
+
|link|
|
121
|
+
elems << link if link.auditable.to_s.substring?( @id )
|
122
|
+
}
|
123
|
+
|
124
|
+
return elems
|
125
|
+
end
|
126
|
+
|
127
|
+
def skip?( elem )
|
128
|
+
@@logged.include?( "#{elem.action}::#{elem.altered}::#{elem.type}" )
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.info
|
132
|
+
{ :name => 'Profiler' }
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# @param [Arachni::Framework] framework
|
139
|
+
# @param [Hash] options options passed to the plugin
|
140
|
+
#
|
141
|
+
def initialize( framework, options )
|
142
|
+
@framework = framework
|
143
|
+
@http = framework.http
|
144
|
+
@options = options
|
145
|
+
end
|
146
|
+
|
147
|
+
def prepare
|
148
|
+
|
149
|
+
Struct.new( 'Body', :type, :method, :raw, :auditable )
|
150
|
+
|
151
|
+
@inputs = []
|
152
|
+
@times = []
|
153
|
+
end
|
154
|
+
|
155
|
+
def run
|
156
|
+
|
157
|
+
@http.add_on_complete {
|
158
|
+
|res|
|
159
|
+
@times << {
|
160
|
+
'url' => res.effective_url,
|
161
|
+
'method' => res.request.method.to_s.upcase,
|
162
|
+
'params' => res.request.params,
|
163
|
+
'time' => res.start_transfer_time
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
@framework.add_on_run_mods {
|
168
|
+
|page|
|
169
|
+
|
170
|
+
Auditor.new( page ).run {
|
171
|
+
|taint, res, elem, found_in|
|
172
|
+
log( taint, res, elem, found_in )
|
173
|
+
}
|
174
|
+
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
def clean_up
|
179
|
+
::IO.select( nil, nil, nil, 1 ) while( @framework.running? )
|
180
|
+
|
181
|
+
register_results( { 'inputs' => @inputs, 'times' => @times } )
|
182
|
+
end
|
183
|
+
|
184
|
+
def log( taint, res, elem, landed_elems )
|
185
|
+
|
186
|
+
res_hash = res.to_hash
|
187
|
+
res_hash['headers'] = res_hash['headers_hash']
|
188
|
+
|
189
|
+
result = {
|
190
|
+
'taint' => taint,
|
191
|
+
'element' => {
|
192
|
+
'type' => elem.type,
|
193
|
+
'auditable' => elem.auditable,
|
194
|
+
'name' => elem.raw['attrs'] ? elem.raw['attrs']['name'] : nil,
|
195
|
+
'altered' => elem.altered,
|
196
|
+
'owner' => elem.url,
|
197
|
+
'action' => elem.action,
|
198
|
+
'method' => elem.method ? elem.method.upcase : nil,
|
199
|
+
},
|
200
|
+
'response' => res_hash,
|
201
|
+
'request' => {
|
202
|
+
'url' => res.request.url,
|
203
|
+
'method' => res.request.method.to_s.upcase,
|
204
|
+
'params' => res.request.params,
|
205
|
+
'headers' => res.request.headers,
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
result['landed'] = landed_elems.map {
|
210
|
+
|elem|
|
211
|
+
{
|
212
|
+
'type' => elem.type,
|
213
|
+
'method' => elem.method ? elem.method.upcase : nil,
|
214
|
+
'name' => elem.raw['attrs'] ? elem.raw['attrs']['name'] : nil,
|
215
|
+
'auditable' => elem.auditable
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
@inputs << result
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.info
|
223
|
+
{
|
224
|
+
:name => 'Profiler',
|
225
|
+
:description => %q{Examines the behavior of the web application gathering general statistics
|
226
|
+
and performs taint analysis to determine which inputs affect the output.
|
227
|
+
|
228
|
+
It's does not perform any vulnerability assesment nor does it send attack payloads.},
|
229
|
+
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
|
230
|
+
:version => '0.1'
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
end
|