arachni 0.2.2.1
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/ACKNOWLEDGMENTS.md +14 -0
- data/AUTHORS.md +6 -0
- data/CHANGELOG.md +162 -0
- data/CONTRIBUTORS.md +10 -0
- data/EXPLOITATION.md +429 -0
- data/HACKING.md +101 -0
- data/LICENSE.md +341 -0
- data/README.md +350 -0
- data/Rakefile +86 -0
- data/bin/arachni +22 -0
- data/bin/arachni_web +77 -0
- data/bin/arachni_xmlrpc +21 -0
- data/bin/arachni_xmlrpcd +82 -0
- data/bin/arachni_xmlrpcd_monitor +74 -0
- data/conf/README.webui.yaml.txt +44 -0
- data/conf/webui.yaml +11 -0
- data/external/metasploit/LICENSE +24 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_exec.rb +142 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_path_traversal.rb +113 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_php_eval.rb +150 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_php_include.rb +141 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_sqlmap.rb +92 -0
- data/external/metasploit/plugins/arachni.rb +536 -0
- data/getoptslong.rb +241 -0
- data/lib/anemone.rb +2 -0
- data/lib/anemone/cookie_store.rb +35 -0
- data/lib/anemone/core.rb +371 -0
- data/lib/anemone/exceptions.rb +5 -0
- data/lib/anemone/http.rb +144 -0
- data/lib/anemone/page.rb +337 -0
- data/lib/anemone/page_store.rb +160 -0
- data/lib/anemone/storage.rb +34 -0
- data/lib/anemone/storage/base.rb +75 -0
- data/lib/anemone/storage/exceptions.rb +15 -0
- data/lib/anemone/storage/mongodb.rb +89 -0
- data/lib/anemone/storage/pstore.rb +50 -0
- data/lib/anemone/storage/redis.rb +90 -0
- data/lib/anemone/storage/tokyo_cabinet.rb +57 -0
- data/lib/anemone/tentacle.rb +40 -0
- data/lib/arachni.rb +16 -0
- data/lib/audit_store.rb +346 -0
- data/lib/component_manager.rb +293 -0
- data/lib/component_options.rb +395 -0
- data/lib/exceptions.rb +76 -0
- data/lib/framework.rb +637 -0
- data/lib/http.rb +809 -0
- data/lib/issue.rb +302 -0
- data/lib/module.rb +4 -0
- data/lib/module/auditor.rb +455 -0
- data/lib/module/base.rb +188 -0
- data/lib/module/element_db.rb +158 -0
- data/lib/module/key_filler.rb +87 -0
- data/lib/module/manager.rb +87 -0
- data/lib/module/output.rb +68 -0
- data/lib/module/trainer.rb +240 -0
- data/lib/module/utilities.rb +110 -0
- data/lib/options.rb +547 -0
- data/lib/parser.rb +2 -0
- data/lib/parser/auditable.rb +522 -0
- data/lib/parser/elements.rb +296 -0
- data/lib/parser/page.rb +149 -0
- data/lib/parser/parser.rb +717 -0
- data/lib/plugin.rb +4 -0
- data/lib/plugin/base.rb +110 -0
- data/lib/plugin/manager.rb +162 -0
- data/lib/report.rb +4 -0
- data/lib/report/base.rb +119 -0
- data/lib/report/manager.rb +92 -0
- data/lib/rpc/xml/client/base.rb +71 -0
- data/lib/rpc/xml/client/dispatcher.rb +49 -0
- data/lib/rpc/xml/client/instance.rb +88 -0
- data/lib/rpc/xml/server/base.rb +90 -0
- data/lib/rpc/xml/server/dispatcher.rb +357 -0
- data/lib/rpc/xml/server/framework.rb +206 -0
- data/lib/rpc/xml/server/instance.rb +191 -0
- data/lib/rpc/xml/server/module/manager.rb +46 -0
- data/lib/rpc/xml/server/options.rb +124 -0
- data/lib/rpc/xml/server/output.rb +299 -0
- data/lib/rpc/xml/server/plugin/manager.rb +58 -0
- data/lib/ruby.rb +5 -0
- data/lib/ruby/object.rb +32 -0
- data/lib/ruby/string.rb +74 -0
- data/lib/ruby/xmlrpc/server.rb +27 -0
- data/lib/spider.rb +200 -0
- data/lib/typhoeus/request.rb +91 -0
- data/lib/typhoeus/response.rb +34 -0
- data/lib/ui/cli/cli.rb +744 -0
- data/lib/ui/cli/output.rb +279 -0
- data/lib/ui/web/log.rb +82 -0
- data/lib/ui/web/output_stream.rb +94 -0
- data/lib/ui/web/report_manager.rb +222 -0
- data/lib/ui/web/server.rb +903 -0
- data/lib/ui/web/server/db/placeholder +0 -0
- data/lib/ui/web/server/public/banner.png +0 -0
- data/lib/ui/web/server/public/bodybg-small.png +0 -0
- data/lib/ui/web/server/public/bodybg.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/pbar-ani.gif +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/jquery-ui-1.8.9.custom.css +573 -0
- data/lib/ui/web/server/public/favicon.ico +0 -0
- data/lib/ui/web/server/public/footer.jpg +0 -0
- data/lib/ui/web/server/public/icons/error.png +0 -0
- data/lib/ui/web/server/public/icons/info.png +0 -0
- data/lib/ui/web/server/public/icons/ok.png +0 -0
- data/lib/ui/web/server/public/icons/status.png +0 -0
- data/lib/ui/web/server/public/js/jquery-1.4.4.min.js +167 -0
- data/lib/ui/web/server/public/js/jquery-ui-1.8.9.custom.min.js +781 -0
- data/lib/ui/web/server/public/logo.png +0 -0
- data/lib/ui/web/server/public/nav-left.jpg +0 -0
- data/lib/ui/web/server/public/nav-right.jpg +0 -0
- data/lib/ui/web/server/public/nav-selected-left.jpg +0 -0
- data/lib/ui/web/server/public/nav-selected-right.jpg +0 -0
- data/lib/ui/web/server/public/reports/placeholder +1 -0
- data/lib/ui/web/server/public/sidebar-bottom.jpg +0 -0
- data/lib/ui/web/server/public/sidebar-h4.jpg +0 -0
- data/lib/ui/web/server/public/sidebar-top.jpg +0 -0
- data/lib/ui/web/server/public/spider.png +0 -0
- data/lib/ui/web/server/public/style.css +604 -0
- data/lib/ui/web/server/tmp/placeholder +0 -0
- data/lib/ui/web/server/views/dispatcher.erb +85 -0
- data/lib/ui/web/server/views/dispatcher_error.erb +14 -0
- data/lib/ui/web/server/views/error.erb +1 -0
- data/lib/ui/web/server/views/flash.erb +18 -0
- data/lib/ui/web/server/views/home.erb +14 -0
- data/lib/ui/web/server/views/instance.erb +213 -0
- data/lib/ui/web/server/views/layout.erb +95 -0
- data/lib/ui/web/server/views/log.erb +40 -0
- data/lib/ui/web/server/views/modules.erb +71 -0
- data/lib/ui/web/server/views/options.erb +23 -0
- data/lib/ui/web/server/views/output_results.erb +51 -0
- data/lib/ui/web/server/views/plugins.erb +42 -0
- data/lib/ui/web/server/views/report_formats.erb +30 -0
- data/lib/ui/web/server/views/reports.erb +55 -0
- data/lib/ui/web/server/views/settings.erb +120 -0
- data/lib/ui/web/server/views/welcome.erb +38 -0
- data/lib/ui/xmlrpc/dispatcher_monitor.rb +204 -0
- data/lib/ui/xmlrpc/xmlrpc.rb +843 -0
- data/logs/placeholder +0 -0
- data/metamodules/autothrottle.rb +74 -0
- data/metamodules/timeout_notice.rb +118 -0
- data/metamodules/uniformity.rb +98 -0
- data/modules/audit/code_injection.rb +136 -0
- data/modules/audit/code_injection_timing.rb +115 -0
- data/modules/audit/code_injection_timing/payloads.txt +4 -0
- data/modules/audit/csrf.rb +301 -0
- data/modules/audit/ldapi.rb +103 -0
- data/modules/audit/ldapi/errors.txt +26 -0
- data/modules/audit/os_cmd_injection.rb +103 -0
- data/modules/audit/os_cmd_injection/payloads.txt +2 -0
- data/modules/audit/os_cmd_injection_timing.rb +104 -0
- data/modules/audit/os_cmd_injection_timing/payloads.txt +3 -0
- data/modules/audit/path_traversal.rb +141 -0
- data/modules/audit/response_splitting.rb +105 -0
- data/modules/audit/rfi.rb +193 -0
- data/modules/audit/sqli.rb +120 -0
- data/modules/audit/sqli/regexp_ids.txt +90 -0
- data/modules/audit/sqli_blind_rdiff.rb +321 -0
- data/modules/audit/sqli_blind_timing.rb +103 -0
- data/modules/audit/sqli_blind_timing/payloads.txt +51 -0
- data/modules/audit/trainer.rb +89 -0
- data/modules/audit/unvalidated_redirect.rb +90 -0
- data/modules/audit/xpath.rb +104 -0
- data/modules/audit/xpath/errors.txt +26 -0
- data/modules/audit/xss.rb +99 -0
- data/modules/audit/xss_event.rb +134 -0
- data/modules/audit/xss_path.rb +125 -0
- data/modules/audit/xss_script_tag.rb +112 -0
- data/modules/audit/xss_tag.rb +112 -0
- data/modules/audit/xss_uri.rb +125 -0
- data/modules/recon/allowed_methods.rb +104 -0
- data/modules/recon/backdoors.rb +131 -0
- data/modules/recon/backdoors/filenames.txt +16 -0
- data/modules/recon/backup_files.rb +177 -0
- data/modules/recon/backup_files/extensions.txt +28 -0
- data/modules/recon/common_directories.rb +138 -0
- data/modules/recon/common_directories/directories.txt +265 -0
- data/modules/recon/common_files.rb +138 -0
- data/modules/recon/common_files/filenames.txt +17 -0
- data/modules/recon/directory_listing.rb +171 -0
- data/modules/recon/grep/captcha.rb +62 -0
- data/modules/recon/grep/credit_card.rb +85 -0
- data/modules/recon/grep/cvs_svn_users.rb +73 -0
- data/modules/recon/grep/emails.rb +59 -0
- data/modules/recon/grep/html_objects.rb +53 -0
- data/modules/recon/grep/private_ip.rb +54 -0
- data/modules/recon/grep/ssn.rb +53 -0
- data/modules/recon/htaccess_limit.rb +82 -0
- data/modules/recon/http_put.rb +95 -0
- data/modules/recon/interesting_responses.rb +118 -0
- data/modules/recon/unencrypted_password_forms.rb +119 -0
- data/modules/recon/webdav.rb +126 -0
- data/modules/recon/xst.rb +107 -0
- data/path_extractors/anchors.rb +35 -0
- data/path_extractors/forms.rb +35 -0
- data/path_extractors/frames.rb +38 -0
- data/path_extractors/generic.rb +39 -0
- data/path_extractors/links.rb +35 -0
- data/path_extractors/meta_refresh.rb +39 -0
- data/path_extractors/scripts.rb +37 -0
- data/path_extractors/sitemap.rb +31 -0
- data/plugins/autologin.rb +137 -0
- data/plugins/content_types.rb +90 -0
- data/plugins/cookie_collector.rb +99 -0
- data/plugins/form_dicattack.rb +185 -0
- data/plugins/healthmap.rb +94 -0
- data/plugins/http_dicattack.rb +133 -0
- data/plugins/metamodules.rb +118 -0
- data/plugins/proxy.rb +248 -0
- data/plugins/proxy/server.rb +66 -0
- data/plugins/waf_detector.rb +184 -0
- data/profiles/comprehensive.afp +74 -0
- data/profiles/full.afp +75 -0
- data/reports/afr.rb +59 -0
- data/reports/ap.rb +55 -0
- data/reports/html.rb +179 -0
- data/reports/html/default.erb +967 -0
- data/reports/metareport.rb +139 -0
- data/reports/metareport/arachni_metareport.rb +174 -0
- data/reports/plugin_formatters/html/content_types.rb +82 -0
- data/reports/plugin_formatters/html/cookie_collector.rb +66 -0
- data/reports/plugin_formatters/html/form_dicattack.rb +54 -0
- data/reports/plugin_formatters/html/healthmap.rb +76 -0
- data/reports/plugin_formatters/html/http_dicattack.rb +54 -0
- data/reports/plugin_formatters/html/metaformatters/timeout_notice.rb +65 -0
- data/reports/plugin_formatters/html/metaformatters/uniformity.rb +71 -0
- data/reports/plugin_formatters/html/metamodules.rb +93 -0
- data/reports/plugin_formatters/html/waf_detector.rb +54 -0
- data/reports/plugin_formatters/stdout/content_types.rb +73 -0
- data/reports/plugin_formatters/stdout/cookie_collector.rb +61 -0
- data/reports/plugin_formatters/stdout/form_dicattack.rb +52 -0
- data/reports/plugin_formatters/stdout/healthmap.rb +72 -0
- data/reports/plugin_formatters/stdout/http_dicattack.rb +53 -0
- data/reports/plugin_formatters/stdout/metaformatters/timeout_notice.rb +55 -0
- data/reports/plugin_formatters/stdout/metaformatters/uniformity.rb +68 -0
- data/reports/plugin_formatters/stdout/metamodules.rb +89 -0
- data/reports/plugin_formatters/stdout/waf_detector.rb +48 -0
- data/reports/plugin_formatters/xml/content_types.rb +91 -0
- data/reports/plugin_formatters/xml/cookie_collector.rb +70 -0
- data/reports/plugin_formatters/xml/form_dicattack.rb +57 -0
- data/reports/plugin_formatters/xml/healthmap.rb +82 -0
- data/reports/plugin_formatters/xml/http_dicattack.rb +57 -0
- data/reports/plugin_formatters/xml/metaformatters/timeout_notice.rb +67 -0
- data/reports/plugin_formatters/xml/metaformatters/uniformity.rb +82 -0
- data/reports/plugin_formatters/xml/metamodules.rb +91 -0
- data/reports/plugin_formatters/xml/waf_detector.rb +58 -0
- data/reports/stdout.rb +182 -0
- data/reports/txt.rb +77 -0
- data/reports/xml.rb +231 -0
- data/reports/xml/buffer.rb +98 -0
- metadata +516 -0
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
|
|
13
|
+
#
|
|
14
|
+
# Namespace under which all meta modules will reside
|
|
15
|
+
#
|
|
16
|
+
module MetaModules
|
|
17
|
+
class Base
|
|
18
|
+
include Arachni::Module::Output
|
|
19
|
+
|
|
20
|
+
def initialize( framework )
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def prepare
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def run
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def clean_up
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.info
|
|
33
|
+
{
|
|
34
|
+
:name => '[Meta] ' + self.name.to_s.gsub( 'Arachni::MetaModules::', '' ),
|
|
35
|
+
:description => %q{Performs high-level meta-analysis on the results of the scan
|
|
36
|
+
using abstract meta components.},
|
|
37
|
+
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
|
|
38
|
+
:version => '0.1',
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
module Plugins
|
|
47
|
+
|
|
48
|
+
#
|
|
49
|
+
# Performs high-level meta-analysis on the results of the scan
|
|
50
|
+
# using abstract meta-modules.
|
|
51
|
+
#
|
|
52
|
+
# @author: Tasos "Zapotek" Laskos
|
|
53
|
+
# <tasos.laskos@gmail.com>
|
|
54
|
+
# <zapotek@segfault.gr>
|
|
55
|
+
# @version: 0.1
|
|
56
|
+
#
|
|
57
|
+
class MetaModules < Arachni::Plugin::Base
|
|
58
|
+
|
|
59
|
+
def initialize( framework, options )
|
|
60
|
+
@framework = framework
|
|
61
|
+
|
|
62
|
+
@metamanager = Arachni::ComponentManager.new( @framework.opts.dir['root'] + 'metamodules/', Arachni::MetaModules )
|
|
63
|
+
|
|
64
|
+
# load all meta-components
|
|
65
|
+
@metamanager.load( ['*'] )
|
|
66
|
+
@inited = {}
|
|
67
|
+
@metamanager.each {
|
|
68
|
+
|name, klass|
|
|
69
|
+
@inited[name] = klass.new( @framework )
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def prepare
|
|
74
|
+
# prepare all meta modules here to give them a chance to set up their hooks
|
|
75
|
+
# and callbacks to other framework interfaces.
|
|
76
|
+
@inited.values.each { |meta| meta.prepare }
|
|
77
|
+
|
|
78
|
+
# we need to wait until the framework has finished running
|
|
79
|
+
# in order to work with the full report
|
|
80
|
+
while( @framework.running? )
|
|
81
|
+
::IO.select( nil, nil, nil, 1 )
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def run
|
|
87
|
+
results = { }
|
|
88
|
+
# run all meta-modules
|
|
89
|
+
@inited.each_pair {
|
|
90
|
+
|name, meta|
|
|
91
|
+
if (metaresult = meta.run) && !metaresult.empty?
|
|
92
|
+
results[name] = { :results => metaresult }.merge( meta.class.info )
|
|
93
|
+
end
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
register_results( results )
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def clean_up
|
|
100
|
+
# let the meta-modules clean up after themselves
|
|
101
|
+
@inited.values.each { |meta| meta.clean_up }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.info
|
|
105
|
+
{
|
|
106
|
+
:name => 'Metamodules',
|
|
107
|
+
:description => %q{Performs high-level meta-analysis on the results of the scan using abstract meta-modules.
|
|
108
|
+
Before reviewing the scan results you are strongly encouraged to take full advantage of the data gathered via meta-analysis.
|
|
109
|
+
They will help you shed light into the inner workings of the web application and even caution you about possible false positives and/or inconclusive test results.},
|
|
110
|
+
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
|
|
111
|
+
:version => '0.1'
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/plugins/proxy.rb
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
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
|
+
# Passive proxy.
|
|
16
|
+
#
|
|
17
|
+
# Will gather data based on user actions and exchanged HTTP traffic and push that
|
|
18
|
+
# data to the {Framework#page_queue} to be audited.
|
|
19
|
+
#
|
|
20
|
+
# @author: Tasos "Zapotek" Laskos
|
|
21
|
+
# <tasos.laskos@gmail.com>
|
|
22
|
+
# <zapotek@segfault.gr>
|
|
23
|
+
# @version: 0.1
|
|
24
|
+
#
|
|
25
|
+
class Proxy < Arachni::Plugin::Base
|
|
26
|
+
|
|
27
|
+
SHUTDOWN_URL = 'http://arachni.proxy.shutdown/'
|
|
28
|
+
|
|
29
|
+
MSG_SHUTDOWN = 'Shutting down the Arachni proxy plug-in...'
|
|
30
|
+
|
|
31
|
+
MSG_DISALOWED = "You can't access this resource via the Arachni " +
|
|
32
|
+
"proxy plug-in for the following reasons:"
|
|
33
|
+
|
|
34
|
+
MSG_NOT_IN_DOMAIN = 'This resource is on a domain or subdomain' +
|
|
35
|
+
' outside the scope of the audit.'
|
|
36
|
+
|
|
37
|
+
MSG_EXCLUDED = 'This resource is matched by an exclude rule.'
|
|
38
|
+
|
|
39
|
+
MSG_NOT_INCLUDED = 'This resource is disallowed based on an include rule.'
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# @param [Arachni::Framework] framework
|
|
43
|
+
# @param [Hash] options options passed to the plugin
|
|
44
|
+
#
|
|
45
|
+
def initialize( framework, options )
|
|
46
|
+
@framework = framework
|
|
47
|
+
@options = options
|
|
48
|
+
|
|
49
|
+
# don't let the framework run just yet
|
|
50
|
+
@framework.pause!
|
|
51
|
+
print_info( "System paused." )
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def prepare
|
|
55
|
+
require @framework.opts.dir['plugins'] + '/proxy/server.rb'
|
|
56
|
+
|
|
57
|
+
# foo initialization, we just need it to verify URLs
|
|
58
|
+
@parser = Arachni::Parser.new( @framework.opts,
|
|
59
|
+
Typhoeus::Response.new(
|
|
60
|
+
:effective_url => @framework.opts.url.to_s,
|
|
61
|
+
:body => '',
|
|
62
|
+
:headers_hash => {}
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@server = Server.new(
|
|
67
|
+
:BindAddress => @options['bind_address'],
|
|
68
|
+
:Port => @options['port'],
|
|
69
|
+
:ProxyVia => false,
|
|
70
|
+
:ProxyContentHandler => method( :handler ),
|
|
71
|
+
:ProxyURITest => method( :allowed? ),
|
|
72
|
+
:AccessLog => [],
|
|
73
|
+
:Logger => WEBrick::Log::new( "/dev/null", 7 )
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def run( )
|
|
78
|
+
print_status( "Listening on: " +
|
|
79
|
+
"http://#{@server[:BindAddress]}:#{@server[:Port]}" )
|
|
80
|
+
|
|
81
|
+
print_status( "Shutdown URL: #{SHUTDOWN_URL}" )
|
|
82
|
+
print_info( "The scan will resume once you visit the shutdown URL." )
|
|
83
|
+
@server.start
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# Called by the proxy to process each page
|
|
88
|
+
#
|
|
89
|
+
def handler( req, res )
|
|
90
|
+
|
|
91
|
+
if( res.header['content-encoding'] == 'gzip' )
|
|
92
|
+
res.header.delete( 'content-encoding' )
|
|
93
|
+
res.body = Zlib::GzipReader.new( StringIO.new( res.body ) ).read
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
headers = {}
|
|
97
|
+
headers.merge( res.header.dup ) if res.header
|
|
98
|
+
headers['set-cookie'] = res.cookies if !res.cookies.empty?
|
|
99
|
+
|
|
100
|
+
# proper initialization in order to parse the response into a page
|
|
101
|
+
@parser = Arachni::Parser.new( @framework.opts,
|
|
102
|
+
Typhoeus::Response.new(
|
|
103
|
+
:effective_url => req.unparsed_uri,
|
|
104
|
+
:body => res.body,
|
|
105
|
+
:headers_hash => headers
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
page = @parser.run
|
|
110
|
+
page = update_forms( page, req ) if req.body
|
|
111
|
+
page.method = res.request_method
|
|
112
|
+
page.code = res.status
|
|
113
|
+
|
|
114
|
+
print_info " * #{page.forms.size} forms"
|
|
115
|
+
print_info " * #{page.links.size} links"
|
|
116
|
+
print_info " * #{page.cookies.size} cookies"
|
|
117
|
+
|
|
118
|
+
update_framework_cookies( page, req )
|
|
119
|
+
@framework.page_queue << page.dup
|
|
120
|
+
|
|
121
|
+
return res
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def update_framework_cookies( page, req )
|
|
125
|
+
|
|
126
|
+
print_debug( 'Updating framework cookies...' )
|
|
127
|
+
|
|
128
|
+
cookies = {}
|
|
129
|
+
|
|
130
|
+
if req['Cookie']
|
|
131
|
+
req['Cookie'].split( ';' ).each{
|
|
132
|
+
|cookie|
|
|
133
|
+
k, v = cookie.split( '=', 2 )
|
|
134
|
+
cookies[k.strip] = v.strip
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
page.cookies.each {
|
|
139
|
+
|cookie|
|
|
140
|
+
cookies.merge!( cookie.simple )
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if cookies.empty?
|
|
144
|
+
print_debug( 'Could not extract cookies...' )
|
|
145
|
+
return
|
|
146
|
+
else
|
|
147
|
+
print_debug( 'Extracted cookies:' )
|
|
148
|
+
cookies.each{
|
|
149
|
+
|k, v|
|
|
150
|
+
print_debug( " * #{k} => #{v}" )
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
@framework.http.update_cookies( cookies )
|
|
155
|
+
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def update_forms( page, req )
|
|
159
|
+
params = {}
|
|
160
|
+
|
|
161
|
+
URI.decode( req.body ).split( '&' ).each {
|
|
162
|
+
|param|
|
|
163
|
+
k,v = param.split( '=', 2 )
|
|
164
|
+
params[k] = v
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
raw = {
|
|
168
|
+
'attrs' => {
|
|
169
|
+
'action' => req.unparsed_uri,
|
|
170
|
+
'method' => req.request_method,
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
form = ::Arachni::Parser::Element::Form.new( req.unparsed_uri, raw )
|
|
175
|
+
form.auditable = params
|
|
176
|
+
|
|
177
|
+
page.forms << form
|
|
178
|
+
|
|
179
|
+
return page
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
#
|
|
183
|
+
# Checks if the URL is allowed.
|
|
184
|
+
#
|
|
185
|
+
# URLs outside the scope of the scan are not allowed.
|
|
186
|
+
#
|
|
187
|
+
def allowed?( uri )
|
|
188
|
+
|
|
189
|
+
url = URI( uri )
|
|
190
|
+
|
|
191
|
+
print_status( 'Requesting: ' + url.to_s )
|
|
192
|
+
|
|
193
|
+
# if !(url.to_s =~ /http(s):\/\//)
|
|
194
|
+
# url = URI( @framework.opts.url.scheme + '://' + url.to_s )
|
|
195
|
+
# end
|
|
196
|
+
|
|
197
|
+
reasons = []
|
|
198
|
+
|
|
199
|
+
if shutdown?( url )
|
|
200
|
+
print_status( 'Shutting down...' )
|
|
201
|
+
@server.shutdown
|
|
202
|
+
reasons << MSG_SHUTDOWN
|
|
203
|
+
return reasons
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
@parser.url = @framework.opts.url
|
|
207
|
+
|
|
208
|
+
reasons << MSG_NOT_IN_DOMAIN if !@parser.in_domain?( url )
|
|
209
|
+
reasons << MSG_EXCLUDED if @parser.exclude?( url )
|
|
210
|
+
reasons << MSG_NOT_INCLUDED if !@parser.include?( url )
|
|
211
|
+
|
|
212
|
+
if !reasons.empty?
|
|
213
|
+
print_info( "#{MSG_DISALOWED}" )
|
|
214
|
+
reasons.each{ |msg| print_info " * #{msg}" }
|
|
215
|
+
reasons << MSG_DISALOWED
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
return reasons
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def shutdown?( url )
|
|
222
|
+
return url.to_s == SHUTDOWN_URL
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def clean_up
|
|
226
|
+
@framework.resume!
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def self.info
|
|
230
|
+
{
|
|
231
|
+
:name => 'Proxy',
|
|
232
|
+
:description => %q{Gathers data based on user actions and exchanged HTTP
|
|
233
|
+
traffic and pushes that data to the framework's page-queue to be audited.
|
|
234
|
+
It also updates the framework cookies with the cookies of the HTTP requests and
|
|
235
|
+
responses, thus it can also be used to login to a web application.},
|
|
236
|
+
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
|
|
237
|
+
:version => '0.1',
|
|
238
|
+
:options => [
|
|
239
|
+
Arachni::OptPort.new( 'port', [ false, 'Port to bind to.', 8282 ] ),
|
|
240
|
+
Arachni::OptAddress.new( 'bind_address', [ false, 'IP address to bind to.', '0.0.0.0' ] )
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
require 'webrick/httpproxy'
|
|
12
|
+
require 'stringio'
|
|
13
|
+
require 'zlib'
|
|
14
|
+
require 'open-uri'
|
|
15
|
+
|
|
16
|
+
module Arachni
|
|
17
|
+
module Plugins
|
|
18
|
+
|
|
19
|
+
class Proxy
|
|
20
|
+
#
|
|
21
|
+
# We add our own type of WEBrick::HTTPProxyServer class that supports
|
|
22
|
+
# notifications when the user tries to access a resource irrelevant
|
|
23
|
+
# to the scan and does not restrict header exchange.
|
|
24
|
+
#
|
|
25
|
+
# @author: Tasos "Zapotek" Laskos
|
|
26
|
+
# <tasos.laskos@gmail.com>
|
|
27
|
+
# <zapotek@segfault.gr>
|
|
28
|
+
# @version: 0.1
|
|
29
|
+
#
|
|
30
|
+
class Server < WEBrick::HTTPProxyServer
|
|
31
|
+
|
|
32
|
+
def choose_header(src, dst)
|
|
33
|
+
connections = split_field(src['connection'])
|
|
34
|
+
src.each{|key, value|
|
|
35
|
+
key = key.downcase
|
|
36
|
+
if HopByHop.member?(key) || # RFC2616: 13.5.1
|
|
37
|
+
connections.member?(key) || # RFC2616: 14.10
|
|
38
|
+
# ShouldNotTransfer.member?(key) # pragmatics
|
|
39
|
+
@logger.debug("choose_header: `#{key}: #{value}'")
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
dst[key] = value
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def service( req, res )
|
|
47
|
+
exclude_reasons = @config[:ProxyURITest].call( req.unparsed_uri )
|
|
48
|
+
|
|
49
|
+
if( exclude_reasons.empty? )
|
|
50
|
+
super( req, res )
|
|
51
|
+
else
|
|
52
|
+
notify( exclude_reasons, req, res )
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def notify( reasons, req, res )
|
|
57
|
+
res.header['content-type'] = 'text/plain'
|
|
58
|
+
res.header.delete( 'content-encoding' )
|
|
59
|
+
|
|
60
|
+
res.body << reasons.map{ |msg| " * #{msg}" }.join( "\n" )
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
# Web Application Firewall detection plugin.
|
|
16
|
+
#
|
|
17
|
+
# This is a 4 stage process:
|
|
18
|
+
# 1. Grab the original page as is
|
|
19
|
+
# 2. Send a lot of innocent strings in non-existent inputs so as to profile normal behavior
|
|
20
|
+
# 3. Send a lot of suspicious strings in non-existent inputs and check if behavior changes
|
|
21
|
+
# 4. Make heads or tails of the gathered responses
|
|
22
|
+
#
|
|
23
|
+
# Steps 1 to 3 will be repeated _precision_ times and the responses will be averaged using rDiff analysis.
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
# @author: Tasos "Zapotek" Laskos
|
|
27
|
+
# <tasos.laskos@gmail.com>
|
|
28
|
+
# <zapotek@segfault.gr>
|
|
29
|
+
# @version: 0.1
|
|
30
|
+
#
|
|
31
|
+
class WAFDetector < Arachni::Plugin::Base
|
|
32
|
+
|
|
33
|
+
MSG_INCONCLUSIVE = %q{Could not establish a baseline behavior for the website. Due to that fact analysis has been aborted.}
|
|
34
|
+
|
|
35
|
+
MSG_FOUND = %q{Request parameters are being filtered, this is usually a sign of a WAF.}
|
|
36
|
+
|
|
37
|
+
MSG_NOT_FOUND = %q{Could not detect any sign of filtering, a WAF doesn't seem to be present.}
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
# @param [Arachni::Framework] framework
|
|
41
|
+
# @param [Hash] options options passed to the plugin
|
|
42
|
+
#
|
|
43
|
+
def initialize( framework, options )
|
|
44
|
+
@framework = framework
|
|
45
|
+
@options = options
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def prepare
|
|
49
|
+
|
|
50
|
+
@precision = @options['precision']
|
|
51
|
+
|
|
52
|
+
bad = [
|
|
53
|
+
'../../../../',
|
|
54
|
+
'<script>foo</script>',
|
|
55
|
+
'\'--;`',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
names = []
|
|
59
|
+
bad.size.times {
|
|
60
|
+
|i|
|
|
61
|
+
names << i.to_s + '_' + Digest::SHA2.hexdigest( rand( i*1000 ).to_s )
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@safe = { }
|
|
65
|
+
@unsafe = { }
|
|
66
|
+
names.each_with_index {
|
|
67
|
+
|name, i|
|
|
68
|
+
@safe[name] = 'value_' + name
|
|
69
|
+
@unsafe[name] = i.to_s + '_' + bad.join( '_' )
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@url = @framework.opts.url
|
|
73
|
+
|
|
74
|
+
@responses = {
|
|
75
|
+
:original => nil,
|
|
76
|
+
:vanilla => nil,
|
|
77
|
+
:spicy => nil
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def run( )
|
|
83
|
+
|
|
84
|
+
print_status( "Starting detection with a precision of #{@precision}." )
|
|
85
|
+
|
|
86
|
+
print_status( "Stage #1: Requesting original page." )
|
|
87
|
+
queue_original( )
|
|
88
|
+
|
|
89
|
+
print_status( "Stage #2: Requesting with vanilla inputs." )
|
|
90
|
+
queue_vanilla( )
|
|
91
|
+
|
|
92
|
+
print_status( "Stage #3: Requesting with spicy inputs." )
|
|
93
|
+
queue_spicy( )
|
|
94
|
+
|
|
95
|
+
print_status( "Stage #4: Analyzing gathered responses." )
|
|
96
|
+
|
|
97
|
+
@framework.http.after_run {
|
|
98
|
+
|
|
99
|
+
if @responses[:original] == @responses[:vanilla]
|
|
100
|
+
if @responses[:vanilla] == @responses[:spicy]
|
|
101
|
+
not_found!
|
|
102
|
+
else
|
|
103
|
+
found!
|
|
104
|
+
end
|
|
105
|
+
else
|
|
106
|
+
inconclusive!
|
|
107
|
+
end
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def found!
|
|
113
|
+
print_ok( MSG_FOUND )
|
|
114
|
+
register_results( { :code => 1, :msg => MSG_FOUND } )
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def not_found!
|
|
118
|
+
print_ok( MSG_NOT_FOUND )
|
|
119
|
+
register_results( { :code => 0, :msg => MSG_NOT_FOUND } )
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def inconclusive!
|
|
123
|
+
print_ok( MSG_INCONCLUSIVE )
|
|
124
|
+
register_results( { :code => -1, :msg => MSG_INCONCLUSIVE } )
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def queue_original
|
|
128
|
+
@precision.times {
|
|
129
|
+
# grab the page containing the login form
|
|
130
|
+
@framework.http.get( @url.to_s ).on_complete {
|
|
131
|
+
|res|
|
|
132
|
+
@responses[:original] ||= res.body
|
|
133
|
+
@responses[:original] = @responses[:original].rdiff( res.body )
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def queue_vanilla( )
|
|
139
|
+
@precision.times {
|
|
140
|
+
# grab the page containing the login form
|
|
141
|
+
@framework.http.get( @url.to_s, :params => @safe ).on_complete {
|
|
142
|
+
|res|
|
|
143
|
+
@responses[:vanilla] ||= res.body
|
|
144
|
+
@responses[:vanilla] = @responses[:vanilla].rdiff( res.body )
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def queue_spicy( )
|
|
150
|
+
@precision.times {
|
|
151
|
+
# grab the page containing the login form
|
|
152
|
+
@framework.http.get( @url.to_s, :params => @unsafe ).on_complete {
|
|
153
|
+
|res|
|
|
154
|
+
@responses[:spicy] ||= res.body
|
|
155
|
+
@responses[:spicy] = @responses[:spicy].rdiff( res.body )
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def self.info
|
|
161
|
+
{
|
|
162
|
+
:name => 'WAF Detector',
|
|
163
|
+
:description => %q{Performs basic profiling on the web application
|
|
164
|
+
in order to assess the existence of a Web Application Firewall.
|
|
165
|
+
|
|
166
|
+
This is a 4 stage process:
|
|
167
|
+
1. Grab the original page as is
|
|
168
|
+
2. Send a lot of innocent (vanilla) strings in non-existent inputs so as to profile normal behavior
|
|
169
|
+
3. Send a lot of suspicious (spicy) strings in non-existent inputs and check if behavior changes
|
|
170
|
+
4. Make heads or tails of the gathered responses
|
|
171
|
+
|
|
172
|
+
Steps 1 to 3 will be repeated _precision_ times (default: 5) and the responses will be averaged using rDiff analysis.},
|
|
173
|
+
:author => 'Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>',
|
|
174
|
+
:version => '0.1',
|
|
175
|
+
:options => [
|
|
176
|
+
Arachni::OptInt.new( 'precision', [ false, 'Stage precision (how many times to perform each detection stage).', 5 ] )
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
end
|
|
184
|
+
end
|