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.
Files changed (44) hide show
  1. data/CHANGELOG.md +30 -0
  2. data/CONTRIBUTORS.md +1 -0
  3. data/README.md +28 -8
  4. data/Rakefile +1 -0
  5. data/bin/arachni_web_autostart +46 -0
  6. data/lib/anemone/page.rb +1 -0
  7. data/lib/arachni.rb +1 -1
  8. data/lib/framework.rb +8 -3
  9. data/lib/http.rb +9 -39
  10. data/lib/mixins/observable.rb +87 -0
  11. data/lib/module/auditor.rb +14 -0
  12. data/lib/module/base.rb +0 -14
  13. data/lib/nokogiri/xml/node.rb +42 -0
  14. data/lib/ui/cli/cli.rb +1 -1
  15. data/lib/ui/web/log.rb +21 -14
  16. data/lib/ui/web/report_manager.rb +100 -15
  17. data/lib/ui/web/server.rb +24 -33
  18. data/lib/ui/web/server/public/reports/demo.testfire.net:Sun Mar 20 02:48:10 2011.afr +104829 -0
  19. data/lib/ui/web/server/views/layout.erb +1 -1
  20. data/lib/ui/web/server/views/options.erb +10 -2
  21. data/lib/ui/web/server/views/plugins.erb +1 -1
  22. data/lib/ui/web/server/views/reports.erb +8 -4
  23. data/lib/ui/xmlrpc/xmlrpc.rb +1 -1
  24. data/metamodules/autothrottle.rb +2 -2
  25. data/metamodules/timeout_notice.rb +1 -1
  26. data/modules/audit/sqli_blind_rdiff.rb +1 -1
  27. data/modules/recon/common_files/filenames.txt +2 -0
  28. data/modules/recon/directory_listing.rb +1 -0
  29. data/modules/recon/interesting_responses.rb +3 -3
  30. data/path_extractors/generic.rb +5 -1
  31. data/plugins/autologin.rb +15 -4
  32. data/plugins/content_types.rb +2 -2
  33. data/plugins/cookie_collector.rb +9 -16
  34. data/plugins/profiler.rb +237 -0
  35. data/reports/html.rb +21 -6
  36. data/reports/html/default.erb +4 -2
  37. data/reports/plugin_formatters/html/autologin.rb +63 -0
  38. data/reports/plugin_formatters/html/profiler.rb +71 -0
  39. data/reports/plugin_formatters/html/profiler/template.erb +177 -0
  40. data/reports/plugin_formatters/stdout/autologin.rb +55 -0
  41. data/reports/plugin_formatters/stdout/profiler.rb +90 -0
  42. data/reports/plugin_formatters/xml/autologin.rb +68 -0
  43. data/reports/plugin_formatters/xml/profiler.rb +120 -0
  44. metadata +23 -68
@@ -12,7 +12,7 @@
12
12
 
13
13
  <script type="text/javascript">
14
14
  function checkAll( type ) {
15
- $( "." + type ).attr( "checked", true )
15
+ $( "." + type + ':not(:disabled)' ).attr( "checked", true )
16
16
  }
17
17
 
18
18
  function uncheckAll( type ) {
@@ -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" <% if opt['default']%> checked="checked" <%end%>
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['host']%></td>
34
- <td><%=report['date']%></td>
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/<%=CGI.escape(report['name'])%>.<%=avail['rep_name']%>"><%=escape(avail['name'])%></a></li>
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/<%=CGI.escape(report['name'])%>/delete" method="post">
49
+ <form action="/report/<%=report.id%>/delete" method="post">
46
50
  <%= csrf_tag %>
47
51
  <input type="submit" value="Delete" />
48
52
  </form>
@@ -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
@@ -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.on_complete {
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
@@ -41,7 +41,7 @@ class TimeoutNotice < Base
41
41
 
42
42
  def prepare
43
43
  # run for each response as it arrives
44
- @http.on_complete {
44
+ @http.add_on_complete {
45
45
  |res|
46
46
 
47
47
  # we don't care about non OK responses
@@ -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.2.2',
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'
@@ -15,3 +15,5 @@ install.php
15
15
  wp-admin/install.php
16
16
  wp-admin/setup-config.php
17
17
  config.php
18
+ php.ini
19
+ error_log
@@ -46,6 +46,7 @@ class DirectoryListing < Arachni::Module::Base
46
46
 
47
47
  def run( )
48
48
 
49
+ return if @page.code != 200
49
50
  path = get_path( @page.url )
50
51
 
51
52
  return if !URI( path ).path || URI( path ).path.gsub( '/', '' ).empty?
@@ -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.on_complete {
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.},
@@ -32,7 +32,11 @@ class Generic < Paths
32
32
  # @return [Array<String>] paths
33
33
  #
34
34
  def run( doc )
35
- URI.extract( doc.to_s )
35
+ begin
36
+ URI.extract( doc.to_s )
37
+ rescue
38
+ return []
39
+ end
36
40
  end
37
41
 
38
42
  end
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
- print_error( 'Could not find a form suiting the provided params at: ' +
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
- print_error( 'Form submitted but no response was returned.' )
87
+ register_results( { :code => -1, :msg => MSG_NO_RESPONSE } )
88
+ print_error( MSG_NO_RESPONSE )
84
89
  return
85
90
  else
86
- print_ok( 'Form submitted successfully.' )
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
@@ -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.on_complete {
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 )
@@ -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.on_complete {
37
- |res|
38
- update( extract_cookies( res ), res )
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 => res.to_hash,
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 )
@@ -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