arachni 0.2.4 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/CHANGELOG.md +33 -0
  2. data/README.md +2 -4
  3. data/Rakefile +15 -4
  4. data/bin/arachni +0 -0
  5. data/bin/arachni_web +0 -0
  6. data/bin/arachni_web_autostart +0 -0
  7. data/bin/arachni_xmlrpc +0 -0
  8. data/bin/arachni_xmlrpcd +0 -0
  9. data/bin/arachni_xmlrpcd_monitor +0 -0
  10. data/lib/arachni.rb +1 -1
  11. data/lib/framework.rb +36 -6
  12. data/lib/http.rb +12 -5
  13. data/lib/module/auditor.rb +482 -59
  14. data/lib/module/base.rb +17 -0
  15. data/lib/module/manager.rb +26 -2
  16. data/lib/module/trainer.rb +1 -12
  17. data/lib/module/utilities.rb +12 -0
  18. data/lib/parser/auditable.rb +8 -3
  19. data/lib/parser/elements.rb +11 -0
  20. data/lib/parser/page.rb +3 -1
  21. data/lib/parser/parser.rb +130 -18
  22. data/lib/rpc/xml/server/dispatcher.rb +21 -0
  23. data/lib/spider.rb +141 -82
  24. data/lib/ui/cli/cli.rb +2 -3
  25. data/lib/ui/web/addon_manager.rb +273 -0
  26. data/lib/ui/web/addons/autodeploy.rb +172 -0
  27. data/lib/ui/web/addons/autodeploy/lib/manager.rb +291 -0
  28. data/lib/ui/web/addons/autodeploy/views/index.erb +124 -0
  29. data/lib/ui/web/addons/sample.rb +78 -0
  30. data/lib/ui/web/addons/sample/views/index.erb +4 -0
  31. data/lib/ui/web/addons/scheduler.rb +139 -0
  32. data/lib/ui/web/addons/scheduler/views/index.erb +131 -0
  33. data/lib/ui/web/addons/scheduler/views/options.erb +93 -0
  34. data/lib/ui/web/dispatcher_manager.rb +80 -13
  35. data/lib/ui/web/instance_manager.rb +87 -0
  36. data/lib/ui/web/scheduler.rb +166 -0
  37. data/lib/ui/web/server.rb +142 -202
  38. data/lib/ui/web/server/public/js/jquery-ui-timepicker.js +985 -0
  39. data/lib/ui/web/server/public/plugins/sample/style.css +0 -0
  40. data/lib/ui/web/server/public/style.css +42 -0
  41. data/lib/ui/web/server/views/addon.erb +15 -0
  42. data/lib/ui/web/server/views/addons.erb +46 -0
  43. data/lib/ui/web/server/views/dispatchers.erb +1 -1
  44. data/lib/ui/web/server/views/instance.erb +9 -11
  45. data/lib/ui/web/server/views/layout.erb +14 -1
  46. data/lib/ui/web/server/views/welcome.erb +7 -6
  47. data/lib/ui/web/utilities.rb +134 -0
  48. data/modules/audit/code_injection_timing.rb +6 -2
  49. data/modules/audit/code_injection_timing/payloads.txt +2 -2
  50. data/modules/audit/os_cmd_injection_timing.rb +7 -3
  51. data/modules/audit/os_cmd_injection_timing/payloads.txt +1 -1
  52. data/modules/audit/sqli_blind_rdiff.rb +18 -233
  53. data/modules/audit/sqli_blind_rdiff/payloads.txt +5 -0
  54. data/modules/audit/sqli_blind_timing.rb +9 -2
  55. data/path_extractors/anchors.rb +1 -1
  56. data/path_extractors/forms.rb +1 -1
  57. data/path_extractors/frames.rb +1 -1
  58. data/path_extractors/generic.rb +1 -1
  59. data/path_extractors/links.rb +1 -1
  60. data/path_extractors/meta_refresh.rb +1 -1
  61. data/path_extractors/scripts.rb +1 -1
  62. data/path_extractors/sitemap.rb +1 -1
  63. data/plugins/proxy/server.rb +3 -2
  64. data/plugins/waf_detector.rb +0 -3
  65. metadata +37 -34
  66. data/lib/anemone/cookie_store.rb +0 -35
  67. data/lib/anemone/core.rb +0 -371
  68. data/lib/anemone/exceptions.rb +0 -5
  69. data/lib/anemone/http.rb +0 -144
  70. data/lib/anemone/page.rb +0 -338
  71. data/lib/anemone/page_store.rb +0 -160
  72. data/lib/anemone/storage.rb +0 -34
  73. data/lib/anemone/storage/base.rb +0 -75
  74. data/lib/anemone/storage/exceptions.rb +0 -15
  75. data/lib/anemone/storage/mongodb.rb +0 -89
  76. data/lib/anemone/storage/pstore.rb +0 -50
  77. data/lib/anemone/storage/redis.rb +0 -90
  78. data/lib/anemone/storage/tokyo_cabinet.rb +0 -57
  79. data/lib/anemone/tentacle.rb +0 -40
data/lib/module/base.rb CHANGED
@@ -115,6 +115,19 @@ class Base
115
115
  def clean_up( )
116
116
  end
117
117
 
118
+ #
119
+ # ABSTRACT - OPTIONAL
120
+ #
121
+ # Prevents auditting elements that have been previously
122
+ # logged by any of the modules returned by this method.
123
+ #
124
+ # @return [Array] module names
125
+ #
126
+ def redundant
127
+ # [ 'sqli', 'sqli_blind_rdiff' ]
128
+ []
129
+ end
130
+
118
131
  #
119
132
  # ABSTRACT - REQUIRED
120
133
  #
@@ -169,6 +182,10 @@ class Base
169
182
  Arachni::Module::Manager.register_results( results )
170
183
  end
171
184
 
185
+ def set_framework( framework )
186
+ @framework = framework
187
+ end
188
+
172
189
  end
173
190
  end
174
191
  end
@@ -36,7 +36,7 @@ module Module
36
36
  # @author: Tasos "Zapotek" Laskos
37
37
  # <tasos.laskos@gmail.com>
38
38
  # <zapotek@segfault.gr>
39
- # @version: 0.1
39
+ # @version: 0.1.1
40
40
  #
41
41
  class Manager < Arachni::ComponentManager
42
42
 
@@ -48,7 +48,8 @@ class Manager < Arachni::ComponentManager
48
48
  def initialize( opts )
49
49
  super( opts.dir['modules'], Arachni::Modules )
50
50
  @opts = opts
51
- @@results = []
51
+ @@results = []
52
+ @@issue_set = Set.new
52
53
  end
53
54
 
54
55
  #
@@ -60,8 +61,31 @@ class Manager < Arachni::ComponentManager
60
61
  #
61
62
  def self.register_results( results )
62
63
  @@results |= results
64
+ results.each { |issue| @@issue_set << self.issue_set_id_from_issue( issue ) }
63
65
  end
64
66
 
67
+ def self.issue_set_id_from_issue( issue )
68
+ issue_url = URI( issue.url )
69
+ issue_url_str = issue_url.scheme + "://" + issue_url.host + issue_url.path
70
+ return "#{issue.mod_name}:#{issue.elem}:#{issue.var}:#{issue_url_str}"
71
+ end
72
+
73
+ def self.issue_set_id_from_elem( mod_name, elem )
74
+ elem_url = URI( elem.action )
75
+ elem_url_str = elem_url.scheme + "://" + elem_url.host + elem_url.path
76
+
77
+ return "#{mod_name}:#{elem.type}:#{elem.altered}:#{elem_url_str}"
78
+ end
79
+
80
+ def self.issue_set
81
+ @@issue_set
82
+ end
83
+
84
+ def issue_set
85
+ @@issue_set
86
+ end
87
+
88
+
65
89
  #
66
90
  # Class method
67
91
  #
@@ -27,6 +27,7 @@ class Trainer
27
27
 
28
28
  include Output
29
29
  include ElementDB
30
+ include Utilities
30
31
 
31
32
  attr_writer :page
32
33
  attr_accessor :http
@@ -70,18 +71,6 @@ class Trainer
70
71
 
71
72
  end
72
73
 
73
- #
74
- # Decodes URLs to reverse multiple encodes and removes NULL characters
75
- #
76
- def url_sanitize( url )
77
-
78
- while( url =~ /%/ )
79
- url = ( URI.decode( url ).to_s.unpack( 'A*' )[0] )
80
- end
81
-
82
- return URI.encode( url )
83
- end
84
-
85
74
  def follow?( url )
86
75
  @parser.url = @page.url
87
76
 
@@ -24,6 +24,18 @@ module Module
24
24
  #
25
25
  module Utilities
26
26
 
27
+ #
28
+ # Decodes URLs to reverse multiple encodes and removes NULL characters
29
+ #
30
+ def url_sanitize( url )
31
+
32
+ while( url =~ /%/ )
33
+ url = ( URI.decode( url ).to_s.unpack( 'A*' )[0] )
34
+ end
35
+
36
+ return URI.encode( url )
37
+ end
38
+
27
39
  #
28
40
  # Gets path from URL
29
41
  #
@@ -49,6 +49,11 @@ class Auditable
49
49
  @auditor = auditor
50
50
  end
51
51
 
52
+ def get_auditor
53
+ @auditor
54
+ end
55
+
56
+
52
57
  #
53
58
  # Delegate output related methods to the auditor
54
59
  #
@@ -142,7 +147,7 @@ class Auditable
142
147
  return if skip?( elem )
143
148
 
144
149
  # inform the user about what we're auditing
145
- print_status( get_status_str( opts[:altered] ) )
150
+ print_status( get_status_str( opts[:altered] ) ) if !opts[:silent]
146
151
 
147
152
  # submit the element with the injection values
148
153
  req = elem.submit( opts )
@@ -179,7 +184,7 @@ class Auditable
179
184
  var_combo = []
180
185
  if( !hash || hash.size == 0 ) then return [] end
181
186
 
182
- if( self.is_a?( Arachni::Parser::Element::Form ) )
187
+ if( self.is_a?( Arachni::Parser::Element::Form ) && !opts[:skip_orig] )
183
188
 
184
189
  if !audited?( audit_id( Arachni::Parser::Element::Form::FORM_VALUES_ORIGINAL ) )
185
190
  # this is the original hash, in case the default values
@@ -295,7 +300,7 @@ class Auditable
295
300
  print_error( 'Failed to get responses, backing out... ' )
296
301
  next
297
302
  else
298
- print_status( 'Analyzing response #' + res.request.id.to_s + '...' )
303
+ print_status( 'Analyzing response #' + res.request.id.to_s + '...' ) if elem.opts && !elem.opts[:silent]
299
304
  end
300
305
 
301
306
  # call the block, if there's one
@@ -48,6 +48,8 @@ class Base < Arachni::Element::Auditable
48
48
 
49
49
  attr_accessor :auditable
50
50
 
51
+ attr_accessor :orig
52
+
51
53
  #
52
54
  # Relatively 'raw' hash holding the element's attributes, values, etc.
53
55
  #
@@ -113,6 +115,8 @@ class Link < Base
113
115
  @method = 'get'
114
116
 
115
117
  @auditable = @raw['vars']
118
+ @orig = @auditable.deep_clone
119
+ @orig.freeze
116
120
  end
117
121
 
118
122
  def http_request( url, opts )
@@ -155,6 +159,8 @@ class Form < Base
155
159
  @method = @raw['attrs']['method']
156
160
 
157
161
  @auditable = simple['auditable'] || {}
162
+ @orig = @auditable.deep_clone
163
+ @orig.freeze
158
164
  end
159
165
 
160
166
  def http_request( url, opts )
@@ -248,6 +254,9 @@ class Cookie < Base
248
254
  |cookie|
249
255
  Options.instance.exclude_cookies.include?( cookie )
250
256
  }
257
+
258
+ @orig = @auditable.deep_clone
259
+ @orig.freeze
251
260
  end
252
261
 
253
262
  def http_request( url, opts )
@@ -274,6 +283,8 @@ class Header < Base
274
283
  @method = 'header'
275
284
 
276
285
  @auditable = @raw
286
+ @orig = @auditable.deep_clone
287
+ @orig.freeze
277
288
  end
278
289
 
279
290
  def http_request( url, opts )
data/lib/parser/page.rb CHANGED
@@ -19,7 +19,7 @@ class Parser
19
19
  # @author: Tasos "Zapotek" Laskos
20
20
  # <tasos.laskos@gmail.com>
21
21
  # <zapotek@segfault.gr>
22
- # @version: 0.2
22
+ # @version: 0.2.1
23
23
  #
24
24
  class Page
25
25
 
@@ -60,6 +60,8 @@ class Page
60
60
  #
61
61
  attr_accessor :response_headers
62
62
 
63
+ attr_accessor :paths
64
+
63
65
  #
64
66
  # @see Parser#links
65
67
  #
data/lib/parser/parser.rb CHANGED
@@ -10,9 +10,11 @@
10
10
  module Arachni
11
11
 
12
12
  opts = Arachni::Options.instance
13
+ require 'webrick'
13
14
  require opts.dir['lib'] + 'parser/elements'
14
15
  require opts.dir['lib'] + 'parser/page'
15
16
  require opts.dir['lib'] + 'module/utilities'
17
+ require opts.dir['lib'] + 'component_manager'
16
18
 
17
19
  #
18
20
  # Analyzer class
@@ -41,12 +43,41 @@ require opts.dir['lib'] + 'module/utilities'
41
43
  # @author: Tasos "Zapotek" Laskos
42
44
  # <tasos.laskos@gmail.com>
43
45
  # <zapotek@segfault.gr>
44
- # @version: 0.2
46
+ # @version: 0.2.1
45
47
  #
46
48
  class Parser
47
-
49
+ include Arachni::UI::Output
48
50
  include Arachni::Module::Utilities
49
51
 
52
+ module Extractors
53
+ #
54
+ # Base Spider parser class for modules.
55
+ #
56
+ # The aim of such modules is to extract paths from a webpage for the Spider to follow.
57
+ #
58
+ #
59
+ # @author: Tasos "Zapotek" Laskos
60
+ # <tasos.laskos@gmail.com>
61
+ # <zapotek@segfault.gr>
62
+ # @version: 0.1
63
+ # @abstract
64
+ #
65
+ class Paths
66
+
67
+ #
68
+ # This method must be implemented by all modules and must return an array
69
+ # of paths as plain strings
70
+ #
71
+ # @param [Nokogiri] Nokogiri document
72
+ #
73
+ # @return [Array<String>] paths
74
+ #
75
+ def run( doc )
76
+
77
+ end
78
+ end
79
+ end
80
+
50
81
  #
51
82
  # @return [String] the url of the page
52
83
  #
@@ -68,7 +99,7 @@ class Parser
68
99
  def initialize( opts, res )
69
100
  @opts = opts
70
101
 
71
- @url = res.effective_url
102
+ @url = url_sanitize( res.effective_url )
72
103
  @html = res.body
73
104
  @response_headers = res.headers_hash
74
105
  end
@@ -89,6 +120,7 @@ class Parser
89
120
  :html => @html,
90
121
  :headers => [],
91
122
  :response_headers => @response_headers,
123
+ :paths => [],
92
124
  :forms => [],
93
125
  :links => [],
94
126
  :cookies => [],
@@ -108,14 +140,25 @@ class Parser
108
140
 
109
141
  jar = preped.merge( jar )
110
142
 
143
+ c_links = links
144
+
145
+ if !( vars = link_vars( @url ) ).empty?
146
+ url = to_absolute( @url )
147
+ c_links << Arachni::Parser::Element::Link.new( url, {
148
+ 'href' => url,
149
+ 'vars' => vars
150
+ } )
151
+ end
152
+
111
153
  return Page.new( {
112
154
  :url => @url,
113
155
  :query_vars => link_vars( @url ),
114
156
  :html => @html,
115
157
  :headers => headers(),
116
158
  :response_headers => @response_headers,
159
+ :paths => paths(),
117
160
  :forms => @opts.audit_forms ? forms() : [],
118
- :links => @opts.audit_links ? links() : [],
161
+ :links => @opts.audit_links ? c_links : [],
119
162
  :cookies => merge_with_cookiestore( merge_with_cookiejar( cookies_arr ) ),
120
163
  :cookiejar => jar
121
164
  } )
@@ -258,7 +301,7 @@ class Parser
258
301
  if( !elements[i]['attrs'] || !elements[i]['attrs']['action'] )
259
302
  action = @url.to_s
260
303
  else
261
- action = elements[i]['attrs']['action']
304
+ action = url_sanitize( elements[i]['attrs']['action'] )
262
305
  end
263
306
  action = URI.escape( action ).to_s
264
307
 
@@ -322,8 +365,17 @@ class Parser
322
365
  if( !include?( link['href'] ) ) then next end
323
366
  if !in_domain?( URI.parse( link['href'] ) ) then next end
324
367
 
325
- link['vars'] = link_vars( link['href'] )
368
+ link['vars'] = {}
369
+ link_vars( link['href'] ).each_pair {
370
+ |key, val|
371
+ begin
372
+ link['vars'][key] = url_sanitize( val )
373
+ rescue
374
+ link['vars'][key] = val
375
+ end
376
+ }
326
377
 
378
+ link['href'] = url_sanitize( link['href'] )
327
379
 
328
380
  link_arr << Element::Link.new( @url, link )
329
381
 
@@ -356,11 +408,12 @@ class Parser
356
408
  rescue
357
409
  end
358
410
 
411
+
359
412
  # don't ask me why....
360
413
  if @response_headers.to_s.substring?( 'set-cookie' )
361
414
  begin
362
- cookies << WEBrick::Cookie.parse_set_cookies( @response_headers['Set-Cookie'].to_s )
363
- cookies << WEBrick::Cookie.parse_set_cookies( @response_headers['set-cookie'].to_s )
415
+ cookies << ::WEBrick::Cookie.parse_set_cookies( @response_headers['Set-Cookie'].to_s )
416
+ cookies << ::WEBrick::Cookie.parse_set_cookies( @response_headers['set-cookie'].to_s )
364
417
  rescue
365
418
  return cookies_arr
366
419
  end
@@ -390,6 +443,32 @@ class Parser
390
443
  return cookies_arr
391
444
  end
392
445
 
446
+ def dir( url )
447
+ URI( File.dirname( URI( url.to_s ).path ) + '/' )
448
+ end
449
+
450
+ #
451
+ # Array of distinct links to follow
452
+ #
453
+ # @return [Array<URI>]
454
+ #
455
+ def paths
456
+ return @paths unless @paths.nil?
457
+ @paths = []
458
+ return @paths if !doc
459
+
460
+ run_extractors( ).each {
461
+ |path|
462
+ next if path.nil? or path.empty?
463
+ abs = to_absolute( path ) rescue next
464
+
465
+ @paths << abs if in_domain?( abs )
466
+ }
467
+
468
+ @paths.uniq!
469
+ return @paths
470
+ end
471
+
393
472
  #
394
473
  # Extracts variables and their values from a link
395
474
  #
@@ -434,26 +513,36 @@ class Parser
434
513
  end
435
514
  rescue Exception => e
436
515
  return nil if link.nil?
437
- # return link
438
516
  end
439
517
 
440
518
  # remove anchor
441
- link = URI.encode( link.to_s.gsub( /#[a-zA-Z0-9_-]*$/, '' ) )
519
+ link = URI.encode( link.to_s.gsub( /#[a-zA-Z0-9_-]*$/,'' ) )
442
520
 
443
- begin
444
- relative = URI(link)
445
- url = URI.parse( @url )
521
+ if url = base
522
+ base_url = URI( url )
523
+ else
524
+ base_url = URI( @url )
525
+ end
446
526
 
447
- absolute = url.merge(relative)
527
+ relative = URI( link )
528
+ absolute = base_url.merge( relative )
448
529
 
449
- absolute.path = '/' if absolute.path.empty?
450
- rescue Exception => e
451
- return
452
- end
530
+ absolute.path = '/' if absolute.path && absolute.path.empty?
453
531
 
454
532
  return absolute.to_s
455
533
  end
456
534
 
535
+
536
+ def base
537
+ begin
538
+ tmp = doc.search( '//base[@href]' )
539
+ return tmp[0]['href'].dup
540
+ rescue
541
+ return
542
+ end
543
+ end
544
+
545
+
457
546
  #
458
547
  # Returns +true+ if *uri* is in the same domain as the page, returns
459
548
  # +false+ otherwise
@@ -508,6 +597,29 @@ class Parser
508
597
 
509
598
  private
510
599
 
600
+ #
601
+ # Runs all Spider (path extraction) modules and returns an array of paths
602
+ #
603
+ # @return [Array] paths
604
+ #
605
+ def run_extractors
606
+ lib = @opts.dir['root'] + 'path_extractors/'
607
+
608
+
609
+ begin
610
+ @@manager ||= ::Arachni::ComponentManager.new( lib, Extractors )
611
+
612
+ return @@manager.available.map {
613
+ |name|
614
+ @@manager[name].new.run( doc )
615
+ }.flatten.uniq
616
+
617
+ rescue ::Exception => e
618
+ print_error( e.to_s )
619
+ print_debug_backtrace( e )
620
+ end
621
+ end
622
+
511
623
  #
512
624
  # Merges an array of form inputs with an array of form selects
513
625
  #