arachni 0.2.4 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +33 -0
- data/README.md +2 -4
- data/Rakefile +15 -4
- data/bin/arachni +0 -0
- data/bin/arachni_web +0 -0
- data/bin/arachni_web_autostart +0 -0
- data/bin/arachni_xmlrpc +0 -0
- data/bin/arachni_xmlrpcd +0 -0
- data/bin/arachni_xmlrpcd_monitor +0 -0
- data/lib/arachni.rb +1 -1
- data/lib/framework.rb +36 -6
- data/lib/http.rb +12 -5
- data/lib/module/auditor.rb +482 -59
- data/lib/module/base.rb +17 -0
- data/lib/module/manager.rb +26 -2
- data/lib/module/trainer.rb +1 -12
- data/lib/module/utilities.rb +12 -0
- data/lib/parser/auditable.rb +8 -3
- data/lib/parser/elements.rb +11 -0
- data/lib/parser/page.rb +3 -1
- data/lib/parser/parser.rb +130 -18
- data/lib/rpc/xml/server/dispatcher.rb +21 -0
- data/lib/spider.rb +141 -82
- data/lib/ui/cli/cli.rb +2 -3
- data/lib/ui/web/addon_manager.rb +273 -0
- data/lib/ui/web/addons/autodeploy.rb +172 -0
- data/lib/ui/web/addons/autodeploy/lib/manager.rb +291 -0
- data/lib/ui/web/addons/autodeploy/views/index.erb +124 -0
- data/lib/ui/web/addons/sample.rb +78 -0
- data/lib/ui/web/addons/sample/views/index.erb +4 -0
- data/lib/ui/web/addons/scheduler.rb +139 -0
- data/lib/ui/web/addons/scheduler/views/index.erb +131 -0
- data/lib/ui/web/addons/scheduler/views/options.erb +93 -0
- data/lib/ui/web/dispatcher_manager.rb +80 -13
- data/lib/ui/web/instance_manager.rb +87 -0
- data/lib/ui/web/scheduler.rb +166 -0
- data/lib/ui/web/server.rb +142 -202
- data/lib/ui/web/server/public/js/jquery-ui-timepicker.js +985 -0
- data/lib/ui/web/server/public/plugins/sample/style.css +0 -0
- data/lib/ui/web/server/public/style.css +42 -0
- data/lib/ui/web/server/views/addon.erb +15 -0
- data/lib/ui/web/server/views/addons.erb +46 -0
- data/lib/ui/web/server/views/dispatchers.erb +1 -1
- data/lib/ui/web/server/views/instance.erb +9 -11
- data/lib/ui/web/server/views/layout.erb +14 -1
- data/lib/ui/web/server/views/welcome.erb +7 -6
- data/lib/ui/web/utilities.rb +134 -0
- data/modules/audit/code_injection_timing.rb +6 -2
- data/modules/audit/code_injection_timing/payloads.txt +2 -2
- data/modules/audit/os_cmd_injection_timing.rb +7 -3
- data/modules/audit/os_cmd_injection_timing/payloads.txt +1 -1
- data/modules/audit/sqli_blind_rdiff.rb +18 -233
- data/modules/audit/sqli_blind_rdiff/payloads.txt +5 -0
- data/modules/audit/sqli_blind_timing.rb +9 -2
- data/path_extractors/anchors.rb +1 -1
- data/path_extractors/forms.rb +1 -1
- data/path_extractors/frames.rb +1 -1
- data/path_extractors/generic.rb +1 -1
- data/path_extractors/links.rb +1 -1
- data/path_extractors/meta_refresh.rb +1 -1
- data/path_extractors/scripts.rb +1 -1
- data/path_extractors/sitemap.rb +1 -1
- data/plugins/proxy/server.rb +3 -2
- data/plugins/waf_detector.rb +0 -3
- metadata +37 -34
- data/lib/anemone/cookie_store.rb +0 -35
- data/lib/anemone/core.rb +0 -371
- data/lib/anemone/exceptions.rb +0 -5
- data/lib/anemone/http.rb +0 -144
- data/lib/anemone/page.rb +0 -338
- data/lib/anemone/page_store.rb +0 -160
- data/lib/anemone/storage.rb +0 -34
- data/lib/anemone/storage/base.rb +0 -75
- data/lib/anemone/storage/exceptions.rb +0 -15
- data/lib/anemone/storage/mongodb.rb +0 -89
- data/lib/anemone/storage/pstore.rb +0 -50
- data/lib/anemone/storage/redis.rb +0 -90
- data/lib/anemone/storage/tokyo_cabinet.rb +0 -57
- data/lib/anemone/tentacle.rb +0 -40
data/lib/anemone/page_store.rb
DELETED
@@ -1,160 +0,0 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
|
-
module Anemone
|
4
|
-
class PageStore
|
5
|
-
extend Forwardable
|
6
|
-
|
7
|
-
def_delegators :@storage, :keys, :values, :size, :each
|
8
|
-
|
9
|
-
def initialize(storage = {})
|
10
|
-
@storage = storage
|
11
|
-
end
|
12
|
-
|
13
|
-
# We typically index the hash with a URI,
|
14
|
-
# but convert it to a String for easier retrieval
|
15
|
-
def [](index)
|
16
|
-
@storage[index.to_s]
|
17
|
-
end
|
18
|
-
|
19
|
-
def []=(index, other)
|
20
|
-
@storage[index.to_s] = other
|
21
|
-
end
|
22
|
-
|
23
|
-
def delete(key)
|
24
|
-
@storage.delete key.to_s
|
25
|
-
end
|
26
|
-
|
27
|
-
def has_key?(key)
|
28
|
-
@storage.has_key? key.to_s
|
29
|
-
end
|
30
|
-
|
31
|
-
def each_value
|
32
|
-
each { |key, value| yield value }
|
33
|
-
end
|
34
|
-
|
35
|
-
def values
|
36
|
-
result = []
|
37
|
-
each { |key, value| result << value }
|
38
|
-
result
|
39
|
-
end
|
40
|
-
|
41
|
-
def touch_key(key)
|
42
|
-
self[key] = Page.new(key)
|
43
|
-
end
|
44
|
-
|
45
|
-
def touch_keys(keys)
|
46
|
-
@storage.merge! keys.inject({}) { |h, k| h[k.to_s] = Page.new(k); h }
|
47
|
-
end
|
48
|
-
|
49
|
-
# Does this PageStore contain the specified URL?
|
50
|
-
# HTTP and HTTPS versions of a URL are considered to be the same page.
|
51
|
-
def has_page?(url)
|
52
|
-
schemes = %w(http https)
|
53
|
-
if schemes.include? url.scheme
|
54
|
-
u = url.dup
|
55
|
-
return schemes.any? { |s| u.scheme = s; has_key?(u) }
|
56
|
-
end
|
57
|
-
|
58
|
-
has_key? url
|
59
|
-
end
|
60
|
-
|
61
|
-
#
|
62
|
-
# Use a breadth-first search to calculate the single-source
|
63
|
-
# shortest paths from *root* to all pages in the PageStore
|
64
|
-
#
|
65
|
-
def shortest_paths!(root)
|
66
|
-
root = URI(root) if root.is_a?(String)
|
67
|
-
raise "Root node not found" if !has_key?(root)
|
68
|
-
|
69
|
-
q = Queue.new
|
70
|
-
|
71
|
-
q.enq root
|
72
|
-
root_page = self[root]
|
73
|
-
root_page.depth = 0
|
74
|
-
root_page.visited = true
|
75
|
-
self[root] = root_page
|
76
|
-
while !q.empty?
|
77
|
-
page = self[q.deq]
|
78
|
-
page.links.each do |u|
|
79
|
-
begin
|
80
|
-
link = self[u]
|
81
|
-
next if link.nil? || !link.fetched? || link.visited
|
82
|
-
|
83
|
-
q << u unless link.redirect?
|
84
|
-
link.visited = true
|
85
|
-
link.depth = page.depth + 1
|
86
|
-
self[u] = link
|
87
|
-
|
88
|
-
if link.redirect?
|
89
|
-
u = link.redirect_to
|
90
|
-
redo
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
self
|
97
|
-
end
|
98
|
-
|
99
|
-
#
|
100
|
-
# Removes all Pages from storage where redirect? is true
|
101
|
-
#
|
102
|
-
def uniq!
|
103
|
-
each_value { |page| delete page.url if page.redirect? }
|
104
|
-
self
|
105
|
-
end
|
106
|
-
|
107
|
-
#
|
108
|
-
# If given a single URL (as a String or URI), returns an Array of Pages which link to that URL
|
109
|
-
# If given an Array of URLs, returns a Hash (URI => [Page, Page...]) of Pages linking to those URLs
|
110
|
-
#
|
111
|
-
def pages_linking_to(urls)
|
112
|
-
unless urls.is_a?(Array)
|
113
|
-
urls = [urls]
|
114
|
-
single = true
|
115
|
-
end
|
116
|
-
|
117
|
-
urls.map! do |url|
|
118
|
-
unless url.is_a?(URI)
|
119
|
-
URI(url) rescue nil
|
120
|
-
else
|
121
|
-
url
|
122
|
-
end
|
123
|
-
end
|
124
|
-
urls.compact
|
125
|
-
|
126
|
-
links = {}
|
127
|
-
urls.each { |url| links[url] = [] }
|
128
|
-
values.each do |page|
|
129
|
-
urls.each { |url| links[url] << page if page.links.include?(url) }
|
130
|
-
end
|
131
|
-
|
132
|
-
if single and !links.empty?
|
133
|
-
return links[urls.first]
|
134
|
-
else
|
135
|
-
return links
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
#
|
140
|
-
# If given a single URL (as a String or URI), returns an Array of URLs which link to that URL
|
141
|
-
# If given an Array of URLs, returns a Hash (URI => [URI, URI...]) of URLs linking to those URLs
|
142
|
-
#
|
143
|
-
def urls_linking_to(urls)
|
144
|
-
unless urls.is_a?(Array)
|
145
|
-
urls = [urls] unless urls.is_a?(Array)
|
146
|
-
single = true
|
147
|
-
end
|
148
|
-
|
149
|
-
links = pages_linking_to(urls)
|
150
|
-
links.each { |url, pages| links[url] = pages.map{|p| p.url} }
|
151
|
-
|
152
|
-
if single and !links.empty?
|
153
|
-
return links[urls.first]
|
154
|
-
else
|
155
|
-
return links
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
end
|
160
|
-
end
|
data/lib/anemone/storage.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
module Anemone
|
2
|
-
module Storage
|
3
|
-
|
4
|
-
def self.Hash(*args)
|
5
|
-
hash = Hash.new(*args)
|
6
|
-
# add close method for compatibility with Storage::Base
|
7
|
-
class << hash; def close; end; end
|
8
|
-
hash
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.PStore(*args)
|
12
|
-
require Arachni::Options.instance.dir['lib'] + 'anemone/storage/pstore'
|
13
|
-
self::PStore.new(*args)
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.TokyoCabinet(file = 'anemone.tch')
|
17
|
-
require Arachni::Options.instance.dir['lib'] + 'anemone/storage/tokyo_cabinet'
|
18
|
-
self::TokyoCabinet.new(file)
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.MongoDB(mongo_db = nil, collection_name = 'pages')
|
22
|
-
require Arachni::Options.instance.dir['lib'] + 'anemone/storage/mongodb'
|
23
|
-
mongo_db ||= Mongo::Connection.new.db('anemone')
|
24
|
-
raise "First argument must be an instance of Mongo::DB" unless mongo_db.is_a?(Mongo::DB)
|
25
|
-
self::MongoDB.new(mongo_db, collection_name)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.Redis(opts = {})
|
29
|
-
require Arachni::Options.instance.dir['lib'] + 'anemone/storage/redis'
|
30
|
-
self::Redis.new(opts)
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
34
|
-
end
|
data/lib/anemone/storage/base.rb
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
require Arachni::Options.instance.dir['lib'] + 'anemone/storage/exceptions'
|
2
|
-
|
3
|
-
module Anemone
|
4
|
-
module Storage
|
5
|
-
class Base
|
6
|
-
|
7
|
-
def initialize(adapter)
|
8
|
-
@adap = adapter
|
9
|
-
|
10
|
-
# verify adapter conforms to this class's methods
|
11
|
-
methods.each do |method|
|
12
|
-
if !@adap.respond_to?(method.to_sym)
|
13
|
-
raise "Storage adapter must support method #{method}"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def [](key)
|
19
|
-
@adap[key]
|
20
|
-
rescue
|
21
|
-
puts key
|
22
|
-
raise RetrievalError, $!
|
23
|
-
end
|
24
|
-
|
25
|
-
def []=(key, value)
|
26
|
-
@adap[key] = value
|
27
|
-
rescue
|
28
|
-
raise InsertionError, $!
|
29
|
-
end
|
30
|
-
|
31
|
-
def delete(key)
|
32
|
-
@adap.delete(key)
|
33
|
-
rescue
|
34
|
-
raise DeletionError, $!
|
35
|
-
end
|
36
|
-
|
37
|
-
def each
|
38
|
-
@adap.each { |k, v| yield k, v }
|
39
|
-
rescue
|
40
|
-
raise GenericError, $!
|
41
|
-
end
|
42
|
-
|
43
|
-
def merge!(hash)
|
44
|
-
@adap.merge!(hash)
|
45
|
-
rescue
|
46
|
-
raise GenericError, $!
|
47
|
-
end
|
48
|
-
|
49
|
-
def close
|
50
|
-
@adap.close
|
51
|
-
rescue
|
52
|
-
raise CloseError, $!
|
53
|
-
end
|
54
|
-
|
55
|
-
def size
|
56
|
-
@adap.size
|
57
|
-
rescue
|
58
|
-
raise GenericError, $!
|
59
|
-
end
|
60
|
-
|
61
|
-
def keys
|
62
|
-
@adap.keys
|
63
|
-
rescue
|
64
|
-
raise GenericError, $!
|
65
|
-
end
|
66
|
-
|
67
|
-
def has_key?(key)
|
68
|
-
@adap.has_key?(key)
|
69
|
-
rescue
|
70
|
-
raise GenericError, $!
|
71
|
-
end
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
@@ -1,89 +0,0 @@
|
|
1
|
-
begin
|
2
|
-
require 'mongo'
|
3
|
-
rescue LoadError
|
4
|
-
puts "You need the mongo gem to use Anemone::Storage::MongoDB"
|
5
|
-
exit
|
6
|
-
end
|
7
|
-
|
8
|
-
module Anemone
|
9
|
-
module Storage
|
10
|
-
class MongoDB
|
11
|
-
|
12
|
-
BINARY_FIELDS = %w(body headers data)
|
13
|
-
|
14
|
-
def initialize(mongo_db, collection_name)
|
15
|
-
@db = mongo_db
|
16
|
-
@collection = @db[collection_name]
|
17
|
-
@collection.remove
|
18
|
-
@collection.create_index 'url'
|
19
|
-
end
|
20
|
-
|
21
|
-
def [](url)
|
22
|
-
if value = @collection.find_one('url' => url.to_s)
|
23
|
-
load_page(value)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def []=(url, page)
|
28
|
-
hash = page.to_hash
|
29
|
-
BINARY_FIELDS.each do |field|
|
30
|
-
hash[field] = BSON::Binary.new(hash[field]) unless hash[field].nil?
|
31
|
-
end
|
32
|
-
@collection.update(
|
33
|
-
{'url' => page.url.to_s},
|
34
|
-
hash,
|
35
|
-
:upsert => true
|
36
|
-
)
|
37
|
-
end
|
38
|
-
|
39
|
-
def delete(url)
|
40
|
-
page = self[url]
|
41
|
-
@collection.remove('url' => url.to_s)
|
42
|
-
page
|
43
|
-
end
|
44
|
-
|
45
|
-
def each
|
46
|
-
@collection.find do |cursor|
|
47
|
-
cursor.each do |doc|
|
48
|
-
page = load_page(doc)
|
49
|
-
yield page.url.to_s, page
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def merge!(hash)
|
55
|
-
hash.each { |key, value| self[key] = value }
|
56
|
-
self
|
57
|
-
end
|
58
|
-
|
59
|
-
def size
|
60
|
-
@collection.count
|
61
|
-
end
|
62
|
-
|
63
|
-
def keys
|
64
|
-
keys = []
|
65
|
-
self.each { |k, v| keys << k.to_s }
|
66
|
-
keys
|
67
|
-
end
|
68
|
-
|
69
|
-
def has_key?(url)
|
70
|
-
!!@collection.find_one('url' => url.to_s)
|
71
|
-
end
|
72
|
-
|
73
|
-
def close
|
74
|
-
@db.connection.close
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
|
79
|
-
def load_page(hash)
|
80
|
-
BINARY_FIELDS.each do |field|
|
81
|
-
hash[field] = hash[field].to_s
|
82
|
-
end
|
83
|
-
Page.from_hash(hash)
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require 'pstore'
|
2
|
-
require 'forwardable'
|
3
|
-
|
4
|
-
module Anemone
|
5
|
-
module Storage
|
6
|
-
class PStore
|
7
|
-
extend Forwardable
|
8
|
-
|
9
|
-
def_delegators :@keys, :has_key?, :keys, :size
|
10
|
-
|
11
|
-
def initialize(file)
|
12
|
-
File.delete(file) if File.exists?(file)
|
13
|
-
@store = ::PStore.new(file)
|
14
|
-
@keys = {}
|
15
|
-
end
|
16
|
-
|
17
|
-
def [](key)
|
18
|
-
@store.transaction { |s| s[key] }
|
19
|
-
end
|
20
|
-
|
21
|
-
def []=(key,value)
|
22
|
-
@keys[key] = nil
|
23
|
-
@store.transaction { |s| s[key] = value }
|
24
|
-
end
|
25
|
-
|
26
|
-
def delete(key)
|
27
|
-
@keys.delete(key)
|
28
|
-
@store.transaction { |s| s.delete key}
|
29
|
-
end
|
30
|
-
|
31
|
-
def each
|
32
|
-
@keys.each_key do |key|
|
33
|
-
value = nil
|
34
|
-
@store.transaction { |s| value = s[key] }
|
35
|
-
yield key, value
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def merge!(hash)
|
40
|
-
@store.transaction do |s|
|
41
|
-
hash.each { |key, value| s[key] = value; @keys[key] = nil }
|
42
|
-
end
|
43
|
-
self
|
44
|
-
end
|
45
|
-
|
46
|
-
def close; end
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,90 +0,0 @@
|
|
1
|
-
require 'redis'
|
2
|
-
|
3
|
-
module Anemone
|
4
|
-
module Storage
|
5
|
-
class Redis
|
6
|
-
|
7
|
-
MARSHAL_FIELDS = %w(links visited fetched)
|
8
|
-
|
9
|
-
def initialize(opts = {})
|
10
|
-
@redis = ::Redis.new(opts)
|
11
|
-
@key_prefix = opts[:key_prefix] || 'anemone'
|
12
|
-
keys.each { |key| delete(key) }
|
13
|
-
end
|
14
|
-
|
15
|
-
def [](key)
|
16
|
-
rkey = "#{@key_prefix}:pages:#{key.to_s}"
|
17
|
-
rget(rkey)
|
18
|
-
end
|
19
|
-
|
20
|
-
def []=(key, value)
|
21
|
-
rkey = "#{@key_prefix}:pages:#{key.to_s}"
|
22
|
-
hash = value.to_hash
|
23
|
-
MARSHAL_FIELDS.each do |field|
|
24
|
-
hash[field] = Marshal.dump(hash[field])
|
25
|
-
end
|
26
|
-
hash.each do |field, value|
|
27
|
-
@redis.hset(rkey, field, value)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def delete(key)
|
32
|
-
rkey = "#{@key_prefix}:pages:#{key.to_s}"
|
33
|
-
page = self[key]
|
34
|
-
@redis.del(rkey)
|
35
|
-
page
|
36
|
-
end
|
37
|
-
|
38
|
-
def each
|
39
|
-
rkeys = @redis.keys("#{@key_prefix}:pages:*")
|
40
|
-
rkeys.each do |rkey|
|
41
|
-
page = rget(rkey)
|
42
|
-
yield page.url.to_s, page
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def merge!(hash)
|
47
|
-
hash.each { |key, value| self[key] = value }
|
48
|
-
self
|
49
|
-
end
|
50
|
-
|
51
|
-
def size
|
52
|
-
@redis.keys("#{@key_prefix}:pages:*").size
|
53
|
-
end
|
54
|
-
|
55
|
-
def keys
|
56
|
-
keys = []
|
57
|
-
self.each { |k, v| keys << k.to_s }
|
58
|
-
keys
|
59
|
-
end
|
60
|
-
|
61
|
-
def has_key?(key)
|
62
|
-
rkey = "#{@key_prefix}:pages:#{key.to_s}"
|
63
|
-
@redis.exists(rkey)
|
64
|
-
end
|
65
|
-
|
66
|
-
def close
|
67
|
-
@redis.quit
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def load_value(hash)
|
73
|
-
MARSHAL_FIELDS.each do |field|
|
74
|
-
unless hash[field].nil? || hash[field] == ''
|
75
|
-
hash[field] = Marshal.load(hash[field])
|
76
|
-
end
|
77
|
-
end
|
78
|
-
Page.from_hash(hash)
|
79
|
-
end
|
80
|
-
|
81
|
-
def rget(rkey)
|
82
|
-
hash = @redis.hgetall(rkey)
|
83
|
-
if !!hash
|
84
|
-
load_value(hash)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|