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
data/lib/http.rb
ADDED
|
@@ -0,0 +1,809 @@
|
|
|
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 'typhoeus'
|
|
12
|
+
|
|
13
|
+
module Arachni
|
|
14
|
+
|
|
15
|
+
require Options.instance.dir['lib'] + 'typhoeus/request'
|
|
16
|
+
require Options.instance.dir['lib'] + 'typhoeus/response'
|
|
17
|
+
require Options.instance.dir['lib'] + 'module/utilities'
|
|
18
|
+
require Options.instance.dir['lib'] + 'module/trainer'
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Arachni::Module::HTTP class
|
|
22
|
+
#
|
|
23
|
+
# Provides a simple, high-performance and thread-safe HTTP interface to modules.
|
|
24
|
+
#
|
|
25
|
+
# All requests are run Async (compliments of Typhoeus)
|
|
26
|
+
# providing great speed and performance.
|
|
27
|
+
#
|
|
28
|
+
# === Exceptions
|
|
29
|
+
# Any exceptions or session corruption is handled by the class.<br/>
|
|
30
|
+
# Some are ignored, on others the HTTP session is refreshed.<br/>
|
|
31
|
+
# Point is, you don't need to worry about it.
|
|
32
|
+
#
|
|
33
|
+
# @author: Tasos "Zapotek" Laskos
|
|
34
|
+
# <tasos.laskos@gmail.com>
|
|
35
|
+
# <zapotek@segfault.gr>
|
|
36
|
+
# @version: 0.2.3
|
|
37
|
+
#
|
|
38
|
+
class HTTP
|
|
39
|
+
|
|
40
|
+
include Arachni::UI::Output
|
|
41
|
+
include Singleton
|
|
42
|
+
include Arachni::Module::Utilities
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# @return [URI]
|
|
46
|
+
#
|
|
47
|
+
attr_reader :last_url
|
|
48
|
+
|
|
49
|
+
#
|
|
50
|
+
# The headers with which the HTTP client is initialized<br/>
|
|
51
|
+
# This is always kept updated.
|
|
52
|
+
#
|
|
53
|
+
# @return [Hash]
|
|
54
|
+
#
|
|
55
|
+
attr_reader :init_headers
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# The user supplied cookie jar
|
|
59
|
+
#
|
|
60
|
+
# @return [Hash]
|
|
61
|
+
#
|
|
62
|
+
attr_reader :cookie_jar
|
|
63
|
+
|
|
64
|
+
attr_reader :request_count
|
|
65
|
+
attr_reader :response_count
|
|
66
|
+
|
|
67
|
+
attr_reader :curr_res_time
|
|
68
|
+
attr_reader :curr_res_cnt
|
|
69
|
+
|
|
70
|
+
attr_reader :trainer
|
|
71
|
+
|
|
72
|
+
def initialize( )
|
|
73
|
+
reset
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def reset
|
|
77
|
+
|
|
78
|
+
opts = Options.instance
|
|
79
|
+
|
|
80
|
+
# someone wants to reset us although nothing has been *set* in the first place
|
|
81
|
+
# otherwise we'd have a url in opts
|
|
82
|
+
return if !opts.url
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
req_limit = opts.http_req_limit
|
|
86
|
+
|
|
87
|
+
hydra_opts = {
|
|
88
|
+
:max_concurrency => req_limit,
|
|
89
|
+
:disable_ssl_peer_verification => true,
|
|
90
|
+
:username => opts.url.user,
|
|
91
|
+
:password => opts.url.password,
|
|
92
|
+
:method => :auto,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@hydra = Typhoeus::Hydra.new( hydra_opts )
|
|
96
|
+
@hydra_sync = Typhoeus::Hydra.new( hydra_opts.merge( :max_concurrency => 1 ) )
|
|
97
|
+
|
|
98
|
+
@hydra.disable_memoization
|
|
99
|
+
@hydra_sync.disable_memoization
|
|
100
|
+
|
|
101
|
+
@trainer = Arachni::Module::Trainer.new
|
|
102
|
+
@trainer.http = self
|
|
103
|
+
|
|
104
|
+
@init_headers = {
|
|
105
|
+
'cookie' => '',
|
|
106
|
+
'From' => opts.authed_by || '',
|
|
107
|
+
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
108
|
+
'User-Agent' => opts.user_agent
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
cookies = {}
|
|
112
|
+
cookies.merge!( self.class.parse_cookiejar( opts.cookie_jar ) ) if opts.cookie_jar
|
|
113
|
+
cookies.merge!( opts.cookies ) if opts.cookies
|
|
114
|
+
|
|
115
|
+
set_cookies( cookies ) if !cookies.empty?
|
|
116
|
+
|
|
117
|
+
proxy_opts = {}
|
|
118
|
+
proxy_opts = {
|
|
119
|
+
:proxy => "#{opts.proxy_addr}:#{opts.proxy_port}",
|
|
120
|
+
:proxy_username => opts.proxy_user,
|
|
121
|
+
:proxy_password => opts.proxy_pass,
|
|
122
|
+
:proxy_type => opts.proxy_type
|
|
123
|
+
} if opts.proxy_addr
|
|
124
|
+
|
|
125
|
+
@opts = {
|
|
126
|
+
:user_agent => opts.user_agent,
|
|
127
|
+
:follow_location => false,
|
|
128
|
+
# :timeout => 8000
|
|
129
|
+
}.merge( proxy_opts )
|
|
130
|
+
|
|
131
|
+
@request_count = 0
|
|
132
|
+
@response_count = 0
|
|
133
|
+
|
|
134
|
+
# we'll use it to identify our requests
|
|
135
|
+
@rand_seed = seed( )
|
|
136
|
+
|
|
137
|
+
@curr_res_time = 0
|
|
138
|
+
@curr_res_cnt = 0
|
|
139
|
+
|
|
140
|
+
@on_complete = []
|
|
141
|
+
@on_queue = []
|
|
142
|
+
|
|
143
|
+
@after_run = []
|
|
144
|
+
@after_run_persistent = []
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
#
|
|
148
|
+
# Runs Hydra (all the asynchronous queued HTTP requests)
|
|
149
|
+
#
|
|
150
|
+
# Should only be called by the framework
|
|
151
|
+
# after all module threads have beed joined!
|
|
152
|
+
#
|
|
153
|
+
def run
|
|
154
|
+
exception_jail {
|
|
155
|
+
@hydra.run
|
|
156
|
+
|
|
157
|
+
@after_run.each {
|
|
158
|
+
|block|
|
|
159
|
+
block.call
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@after_run.clear
|
|
163
|
+
|
|
164
|
+
@after_run_persistent.each {
|
|
165
|
+
|block|
|
|
166
|
+
block.call
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@curr_res_time = 0
|
|
170
|
+
@curr_res_cnt = 0
|
|
171
|
+
}
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def fire_and_forget
|
|
175
|
+
exception_jail {
|
|
176
|
+
@hydra.fire_and_forget
|
|
177
|
+
}
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def abort
|
|
181
|
+
exception_jail {
|
|
182
|
+
@hydra.abort
|
|
183
|
+
}
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def average_res_time
|
|
187
|
+
return 0 if @curr_res_cnt == 0
|
|
188
|
+
return @curr_res_time / @curr_res_cnt
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def max_concurrency!( max_concurrency )
|
|
192
|
+
@hydra.max_concurrency = max_concurrency
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def max_concurrency
|
|
196
|
+
@hydra.max_concurrency
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
#
|
|
200
|
+
# Queues a Tyhpoeus::Request and applies an 'on_complete' callback
|
|
201
|
+
# on behal of the trainer.
|
|
202
|
+
#
|
|
203
|
+
# @param [Tyhpoeus::Request] req the request to queue
|
|
204
|
+
# @param [Bool] async run request async?
|
|
205
|
+
#
|
|
206
|
+
def queue( req, async = true )
|
|
207
|
+
|
|
208
|
+
req.id = @request_count
|
|
209
|
+
|
|
210
|
+
@on_queue.each {
|
|
211
|
+
|block|
|
|
212
|
+
exception_jail{ block.call( req, async ) }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if( !async )
|
|
216
|
+
@hydra_sync.queue( req )
|
|
217
|
+
else
|
|
218
|
+
@hydra.queue( req )
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
@request_count += 1
|
|
222
|
+
|
|
223
|
+
print_debug( '------------' )
|
|
224
|
+
print_debug( 'Queued request.' )
|
|
225
|
+
print_debug( 'ID#: ' + req.id.to_s )
|
|
226
|
+
print_debug( 'URL: ' + req.url )
|
|
227
|
+
print_debug( 'Method: ' + req.method.to_s )
|
|
228
|
+
print_debug( 'Params: ' + req.params.to_s )
|
|
229
|
+
print_debug( 'Headers: ' + req.headers.to_s )
|
|
230
|
+
print_debug( 'Train?: ' + req.train?.to_s )
|
|
231
|
+
print_debug( '------------' )
|
|
232
|
+
|
|
233
|
+
req.on_complete( true ) {
|
|
234
|
+
|res|
|
|
235
|
+
|
|
236
|
+
@response_count += 1
|
|
237
|
+
@curr_res_cnt += 1
|
|
238
|
+
@curr_res_time += res.start_transfer_time
|
|
239
|
+
|
|
240
|
+
@on_complete.each {
|
|
241
|
+
|block|
|
|
242
|
+
exception_jail{ block.call( res ) }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
parse_and_set_cookies( res )
|
|
246
|
+
|
|
247
|
+
print_debug( '------------' )
|
|
248
|
+
print_debug( 'Got response.' )
|
|
249
|
+
print_debug( 'Request ID#: ' + res.request.id.to_s )
|
|
250
|
+
print_debug( 'URL: ' + res.effective_url )
|
|
251
|
+
print_debug( 'Method: ' + res.request.method.to_s )
|
|
252
|
+
print_debug( 'Params: ' + res.request.params.to_s )
|
|
253
|
+
print_debug( 'Headers: ' + res.request.headers.to_s )
|
|
254
|
+
print_debug( 'Train?: ' + res.request.train?.to_s )
|
|
255
|
+
print_debug( '------------' )
|
|
256
|
+
|
|
257
|
+
if( req.train? )
|
|
258
|
+
# handle redirections
|
|
259
|
+
if( ( redir = redirect?( res.dup ) ).is_a?( String ) )
|
|
260
|
+
req2 = get( redir, :remove_id => true )
|
|
261
|
+
req2.on_complete {
|
|
262
|
+
|res2|
|
|
263
|
+
@trainer.add_response( res2, true )
|
|
264
|
+
} if req2
|
|
265
|
+
else
|
|
266
|
+
@trainer.add_response( res )
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
exception_jail {
|
|
272
|
+
@hydra_sync.run if !async
|
|
273
|
+
}
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
#
|
|
277
|
+
# Gets called each time a hydra run finishes
|
|
278
|
+
#
|
|
279
|
+
def after_run( &block )
|
|
280
|
+
@after_run << block
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def after_run_persistent( &block )
|
|
284
|
+
@after_run_persistent << block
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
#
|
|
288
|
+
# Gets called each time a request completes and passes the response
|
|
289
|
+
# to the block
|
|
290
|
+
#
|
|
291
|
+
def on_complete( &block )
|
|
292
|
+
@on_complete << block
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
#
|
|
296
|
+
# Gets called each time a request is queued and passes the request
|
|
297
|
+
# to the block
|
|
298
|
+
#
|
|
299
|
+
def on_queue( &block )
|
|
300
|
+
@on_queue << block
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
#
|
|
304
|
+
# Makes a generic request
|
|
305
|
+
#
|
|
306
|
+
# @param [URI] url
|
|
307
|
+
# @param [Hash] opts
|
|
308
|
+
#
|
|
309
|
+
# @return [Typhoeus::Request]
|
|
310
|
+
#
|
|
311
|
+
def request( url, opts )
|
|
312
|
+
headers = opts[:headers] || {}
|
|
313
|
+
opts[:headers] = @init_headers.dup.merge( headers )
|
|
314
|
+
|
|
315
|
+
train = opts[:train]
|
|
316
|
+
async = opts[:async]
|
|
317
|
+
async = true if async == nil
|
|
318
|
+
|
|
319
|
+
exception_jail {
|
|
320
|
+
|
|
321
|
+
req = Typhoeus::Request.new( normalize_url( url ), opts.merge( @opts ) )
|
|
322
|
+
req.train! if train
|
|
323
|
+
|
|
324
|
+
queue( req, async )
|
|
325
|
+
return req
|
|
326
|
+
}
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
#
|
|
330
|
+
# Gets a URL passing the provided query parameters
|
|
331
|
+
#
|
|
332
|
+
# @param [URI] url URL to GET
|
|
333
|
+
# @param [Hash] opts request options
|
|
334
|
+
# * :params => request parameters || {}
|
|
335
|
+
# * :train => force Arachni to analyze the HTML code || false
|
|
336
|
+
# * :async => make the request async? || true
|
|
337
|
+
# * :headers => HTTP request headers || {}
|
|
338
|
+
# * :follow_location => follow redirects || false
|
|
339
|
+
#
|
|
340
|
+
# @return [Typhoeus::Request]
|
|
341
|
+
#
|
|
342
|
+
def get( url, opts = { } )
|
|
343
|
+
|
|
344
|
+
params = opts[:params] || {}
|
|
345
|
+
remove_id = opts[:remove_id]
|
|
346
|
+
train = opts[:train]
|
|
347
|
+
|
|
348
|
+
follow_location = opts[:follow_location] || false
|
|
349
|
+
|
|
350
|
+
async = opts[:async]
|
|
351
|
+
async = true if async == nil
|
|
352
|
+
|
|
353
|
+
headers = opts[:headers] || {}
|
|
354
|
+
headers = @init_headers.dup.merge( headers )
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
params = params.merge( { @rand_seed => '' } ) if !remove_id
|
|
358
|
+
|
|
359
|
+
#
|
|
360
|
+
# the exception jail function wraps the block passed to it
|
|
361
|
+
# in exception handling and runs it
|
|
362
|
+
#
|
|
363
|
+
# how cool is Ruby? Seriously....
|
|
364
|
+
#
|
|
365
|
+
exception_jail {
|
|
366
|
+
|
|
367
|
+
#
|
|
368
|
+
# There are cases where the url already has a query and we also have
|
|
369
|
+
# some params to work with. Some webapp frameworks will break
|
|
370
|
+
# or get confused...plus the url will not be RFC compliant.
|
|
371
|
+
#
|
|
372
|
+
# Thus we need to merge the provided params with the
|
|
373
|
+
# params of the url query and remove the latter from the url.
|
|
374
|
+
#
|
|
375
|
+
cparams = params.dup
|
|
376
|
+
curl = URI.escape( url.dup )
|
|
377
|
+
|
|
378
|
+
cparams = q_to_h( curl ).merge( cparams )
|
|
379
|
+
|
|
380
|
+
begin
|
|
381
|
+
curl.gsub!( "?#{URI(curl).query}", '' ) if URI(curl).query
|
|
382
|
+
rescue
|
|
383
|
+
return
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
opts = {
|
|
387
|
+
:headers => headers,
|
|
388
|
+
:params => cparams.empty? ? nil : cparams,
|
|
389
|
+
:follow_location => follow_location,
|
|
390
|
+
:timeout => opts[:timeout]
|
|
391
|
+
}.merge( @opts )
|
|
392
|
+
|
|
393
|
+
req = Typhoeus::Request.new( curl, opts )
|
|
394
|
+
req.train! if train
|
|
395
|
+
|
|
396
|
+
queue( req, async )
|
|
397
|
+
return req
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
#
|
|
403
|
+
# Posts a form to a URL with the provided query parameters
|
|
404
|
+
#
|
|
405
|
+
# @param [URI] url URL to POST
|
|
406
|
+
# @param [Hash] opts request options
|
|
407
|
+
# * :params => request parameters || {}
|
|
408
|
+
# * :train => force Arachni to analyze the HTML code || false
|
|
409
|
+
# * :async => make the request async? || true
|
|
410
|
+
# * :headers => HTTP request headers || {}
|
|
411
|
+
#
|
|
412
|
+
# @return [Typhoeus::Request]
|
|
413
|
+
#
|
|
414
|
+
def post( url, opts = { } )
|
|
415
|
+
|
|
416
|
+
params = opts[:params]
|
|
417
|
+
train = opts[:train]
|
|
418
|
+
|
|
419
|
+
async = opts[:async]
|
|
420
|
+
async = true if async == nil
|
|
421
|
+
|
|
422
|
+
headers = opts[:headers] || {}
|
|
423
|
+
headers = @init_headers.dup.merge( headers )
|
|
424
|
+
|
|
425
|
+
exception_jail {
|
|
426
|
+
|
|
427
|
+
opts = {
|
|
428
|
+
:method => :post,
|
|
429
|
+
:headers => headers,
|
|
430
|
+
:params => params,
|
|
431
|
+
:follow_location => false,
|
|
432
|
+
:timeout => opts[:timeout]
|
|
433
|
+
}.merge( @opts )
|
|
434
|
+
|
|
435
|
+
req = Typhoeus::Request.new( normalize_url( url ), opts )
|
|
436
|
+
req.train! if train
|
|
437
|
+
|
|
438
|
+
queue( req, async )
|
|
439
|
+
return req
|
|
440
|
+
}
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
#
|
|
444
|
+
# Sends an HTTP TRACE request to "url".
|
|
445
|
+
#
|
|
446
|
+
# @param [URI] url URL to POST
|
|
447
|
+
# @param [Hash] opts request options
|
|
448
|
+
# * :params => request parameters || {}
|
|
449
|
+
# * :train => force Arachni to analyze the HTML code || false
|
|
450
|
+
# * :async => make the request async? || true
|
|
451
|
+
# * :headers => HTTP request headers || {}
|
|
452
|
+
#
|
|
453
|
+
# @return [Typhoeus::Request]
|
|
454
|
+
#
|
|
455
|
+
def trace( url, opts = { } )
|
|
456
|
+
|
|
457
|
+
params = opts[:params]
|
|
458
|
+
train = opts[:train]
|
|
459
|
+
|
|
460
|
+
async = opts[:async]
|
|
461
|
+
async = true if async == nil
|
|
462
|
+
|
|
463
|
+
headers = opts[:headers] || {}
|
|
464
|
+
headers = @init_headers.dup.merge( headers )
|
|
465
|
+
|
|
466
|
+
exception_jail {
|
|
467
|
+
|
|
468
|
+
opts = {
|
|
469
|
+
:method => :trace,
|
|
470
|
+
:headers => headers,
|
|
471
|
+
:params => params,
|
|
472
|
+
:follow_location => false
|
|
473
|
+
}.merge( @opts )
|
|
474
|
+
|
|
475
|
+
req = Typhoeus::Request.new( normalize_url( url ), opts )
|
|
476
|
+
req.train! if train
|
|
477
|
+
|
|
478
|
+
queue( req, async )
|
|
479
|
+
return req
|
|
480
|
+
}
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
#
|
|
485
|
+
# Gets a url with cookies and url variables
|
|
486
|
+
#
|
|
487
|
+
# @param [URI] url URL to GET
|
|
488
|
+
# @param [Hash] opts request options
|
|
489
|
+
# * :params => cookies || {}
|
|
490
|
+
# * :train => force Arachni to analyze the HTML code || false
|
|
491
|
+
# * :async => make the request async? || true
|
|
492
|
+
# * :headers => HTTP request headers || {}
|
|
493
|
+
#
|
|
494
|
+
# @return [Typhoeus::Request]
|
|
495
|
+
#
|
|
496
|
+
def cookie( url, opts = { } )
|
|
497
|
+
|
|
498
|
+
cookies = opts[:params] || {}
|
|
499
|
+
# params = opts[:params]
|
|
500
|
+
train = opts[:train]
|
|
501
|
+
|
|
502
|
+
async = opts[:async]
|
|
503
|
+
async = true if async == nil
|
|
504
|
+
|
|
505
|
+
headers = opts[:headers] || {}
|
|
506
|
+
|
|
507
|
+
headers = @init_headers.dup.
|
|
508
|
+
merge( { 'cookie' => get_cookies_str( cookies ) } ).merge( headers )
|
|
509
|
+
|
|
510
|
+
# wrap the code in exception handling
|
|
511
|
+
exception_jail {
|
|
512
|
+
|
|
513
|
+
opts = {
|
|
514
|
+
:headers => headers,
|
|
515
|
+
:follow_location => false,
|
|
516
|
+
# :params => params
|
|
517
|
+
:timeout => opts[:timeout]
|
|
518
|
+
}.merge( @opts )
|
|
519
|
+
|
|
520
|
+
req = Typhoeus::Request.new( normalize_url( url ), opts )
|
|
521
|
+
req.train! if train
|
|
522
|
+
|
|
523
|
+
queue( req, async )
|
|
524
|
+
return req
|
|
525
|
+
}
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
#
|
|
529
|
+
# Gets a url with optional url variables and modified headers
|
|
530
|
+
#
|
|
531
|
+
# @param [URI] url URL to GET
|
|
532
|
+
# @param [Hash] opts request options
|
|
533
|
+
# * :params => headers || {}
|
|
534
|
+
# * :train => force Arachni to analyze the HTML code || false
|
|
535
|
+
# * :async => make the request async? || true
|
|
536
|
+
#
|
|
537
|
+
# @return [Typhoeus::Request]
|
|
538
|
+
#
|
|
539
|
+
def header( url, opts = { } )
|
|
540
|
+
|
|
541
|
+
headers = opts[:params] || {}
|
|
542
|
+
# params = opts[:params]
|
|
543
|
+
train = opts[:train]
|
|
544
|
+
|
|
545
|
+
async = opts[:async]
|
|
546
|
+
async = true if async == nil
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
# wrap the code in exception handling
|
|
550
|
+
exception_jail {
|
|
551
|
+
|
|
552
|
+
orig_headers = @init_headers.clone
|
|
553
|
+
@init_headers = @init_headers.merge( headers )
|
|
554
|
+
|
|
555
|
+
req = Typhoeus::Request.new( normalize_url( url ),
|
|
556
|
+
:headers => @init_headers.dup,
|
|
557
|
+
:user_agent => @init_headers['User-Agent'],
|
|
558
|
+
:follow_location => false,
|
|
559
|
+
# :params => params
|
|
560
|
+
:timeout => opts[:timeout]
|
|
561
|
+
)
|
|
562
|
+
req.train! if train
|
|
563
|
+
|
|
564
|
+
@init_headers = orig_headers.clone
|
|
565
|
+
|
|
566
|
+
queue( req, async )
|
|
567
|
+
return req
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def q_to_h( url )
|
|
573
|
+
params = {}
|
|
574
|
+
|
|
575
|
+
begin
|
|
576
|
+
query = URI( url.to_s ).query
|
|
577
|
+
return params if !query
|
|
578
|
+
|
|
579
|
+
query.split( '&' ).each {
|
|
580
|
+
|param|
|
|
581
|
+
k,v = param.split( '=', 2 )
|
|
582
|
+
params[k] = v
|
|
583
|
+
}
|
|
584
|
+
rescue
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
return params
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
def current_cookies
|
|
591
|
+
parse_cookie_str( @init_headers['cookie'] )
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def update_cookies( cookies )
|
|
595
|
+
set_cookies( current_cookies.merge( cookies ) )
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
#
|
|
599
|
+
# Sets cookies for the HTTP session
|
|
600
|
+
#
|
|
601
|
+
# @param [Hash] cookies name=>value pairs
|
|
602
|
+
#
|
|
603
|
+
# @return [void]
|
|
604
|
+
#
|
|
605
|
+
def set_cookies( cookies )
|
|
606
|
+
@init_headers['cookie'] = ''
|
|
607
|
+
@cookie_jar = cookies.each_pair {
|
|
608
|
+
|name, value|
|
|
609
|
+
@init_headers['cookie'] += "#{name}=#{value};"
|
|
610
|
+
}
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def parse_and_set_cookies( res )
|
|
614
|
+
cookie_hash = {}
|
|
615
|
+
|
|
616
|
+
# extract cookies from the header field
|
|
617
|
+
begin
|
|
618
|
+
[res.headers_hash['Set-Cookie']].flatten.each {
|
|
619
|
+
|set_cookie_str|
|
|
620
|
+
|
|
621
|
+
break if !set_cookie_str.is_a?( String )
|
|
622
|
+
cookie_hash.merge!( WEBrick::Cookie.parse_set_cookies(set_cookie_str).inject({}) do |hash, cookie|
|
|
623
|
+
hash[cookie.name] = cookie.value if !!cookie
|
|
624
|
+
hash
|
|
625
|
+
end
|
|
626
|
+
)
|
|
627
|
+
}
|
|
628
|
+
rescue Exception => e
|
|
629
|
+
print_debug( e.to_s )
|
|
630
|
+
print_debug_backtrace( e )
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
# extract cookies from the META tags
|
|
634
|
+
begin
|
|
635
|
+
|
|
636
|
+
# get get the head in order to check if it has an http-equiv for set-cookie
|
|
637
|
+
head = res.body.match( /<head(.*)<\/head>/imx )
|
|
638
|
+
|
|
639
|
+
# if it does feed the head to the parser in order to extract the cookies
|
|
640
|
+
if head && head.to_s.substring?( 'set-cookie' )
|
|
641
|
+
Nokogiri::HTML( head.to_s ).search( "//meta[@http-equiv]" ).each {
|
|
642
|
+
|elem|
|
|
643
|
+
|
|
644
|
+
next if elem['http-equiv'].downcase != 'set-cookie'
|
|
645
|
+
k, v = elem['content'].split( ';' )[0].split( '=', 2 )
|
|
646
|
+
cookie_hash[k] = v
|
|
647
|
+
}
|
|
648
|
+
end
|
|
649
|
+
rescue Exception => e
|
|
650
|
+
print_debug( e.to_s )
|
|
651
|
+
print_debug_backtrace( e )
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
return if cookie_hash.empty?
|
|
655
|
+
|
|
656
|
+
# update framework cookies
|
|
657
|
+
Arachni::Options.instance.cookies = cookie_hash
|
|
658
|
+
|
|
659
|
+
current = parse_cookie_str( @init_headers['cookie'] )
|
|
660
|
+
set_cookies( current.merge( cookie_hash ) )
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
#
|
|
664
|
+
# Returns a hash of cookies as a string (merged with the cookie-jar)
|
|
665
|
+
#
|
|
666
|
+
# @param [Hash] cookies name=>value pairs
|
|
667
|
+
#
|
|
668
|
+
# @return [string]
|
|
669
|
+
#
|
|
670
|
+
def get_cookies_str( cookies = { } )
|
|
671
|
+
|
|
672
|
+
jar = parse_cookie_str( @init_headers['cookie'] )
|
|
673
|
+
cookies = jar.merge( cookies )
|
|
674
|
+
|
|
675
|
+
str = ''
|
|
676
|
+
cookies.each_pair {
|
|
677
|
+
|name, value|
|
|
678
|
+
value = '' if !value
|
|
679
|
+
val = URI.escape( URI.escape( value ), '+;' )
|
|
680
|
+
str += "#{name}=#{val};"
|
|
681
|
+
}
|
|
682
|
+
return str
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
#
|
|
686
|
+
# Converts HTTP cookies from string to Hash
|
|
687
|
+
#
|
|
688
|
+
# @param [String] str
|
|
689
|
+
#
|
|
690
|
+
# @return [Hash]
|
|
691
|
+
#
|
|
692
|
+
def parse_cookie_str( str )
|
|
693
|
+
cookie_jar = Hash.new
|
|
694
|
+
str.split( ';' ).each {
|
|
695
|
+
|kvp|
|
|
696
|
+
cookie_jar[kvp.split( "=" )[0]] = kvp.split( "=" )[1]
|
|
697
|
+
}
|
|
698
|
+
return cookie_jar
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
#
|
|
702
|
+
# Class method
|
|
703
|
+
#
|
|
704
|
+
# Parses netscape HTTP cookie files
|
|
705
|
+
#
|
|
706
|
+
# @param [String] cookie_jar the location of the cookie file
|
|
707
|
+
#
|
|
708
|
+
# @return [Hash] cookies in name=>value pairs
|
|
709
|
+
#
|
|
710
|
+
def self.parse_cookiejar( cookie_jar )
|
|
711
|
+
|
|
712
|
+
cookies = Hash.new
|
|
713
|
+
|
|
714
|
+
jar = File.open( cookie_jar, 'r' )
|
|
715
|
+
jar.each_line {
|
|
716
|
+
|line|
|
|
717
|
+
|
|
718
|
+
# skip empty lines
|
|
719
|
+
if (line = line.strip).size == 0 then next end
|
|
720
|
+
|
|
721
|
+
# skip comment lines
|
|
722
|
+
if line[0] == '#' then next end
|
|
723
|
+
|
|
724
|
+
cookie_arr = line.split( "\t" )
|
|
725
|
+
|
|
726
|
+
cookies[cookie_arr[-2]] = cookie_arr[-1]
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
cookies
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def self.content_type( headers_hash )
|
|
733
|
+
return if !headers_hash.is_a?( Hash )
|
|
734
|
+
|
|
735
|
+
headers_hash.each_pair {
|
|
736
|
+
|key, val|
|
|
737
|
+
return val if key.to_s.downcase == 'content-type'
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
return
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
#
|
|
744
|
+
# Encodes and parses a URL String
|
|
745
|
+
#
|
|
746
|
+
# @param [String] url URL String
|
|
747
|
+
#
|
|
748
|
+
# @return [URI] URI object
|
|
749
|
+
#
|
|
750
|
+
def parse_url( url )
|
|
751
|
+
URI.parse( URI.encode( url ) )
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
#
|
|
755
|
+
# Checks whether or not the provided response is a custom 404 page
|
|
756
|
+
#
|
|
757
|
+
# @param [Typhoeus::Response] res the response to check
|
|
758
|
+
#
|
|
759
|
+
# @param [Bool]
|
|
760
|
+
#
|
|
761
|
+
def custom_404?( res )
|
|
762
|
+
|
|
763
|
+
@_404 ||= {}
|
|
764
|
+
path = get_path( res.effective_url )
|
|
765
|
+
@_404[path] ||= {}
|
|
766
|
+
|
|
767
|
+
if( !@_404[path]['file'] )
|
|
768
|
+
|
|
769
|
+
# force a 404 and grab the html body
|
|
770
|
+
force_404 = path + Digest::SHA1.hexdigest( rand( 9999999 ).to_s )
|
|
771
|
+
@_404[path]['file'] = Typhoeus::Request.get( force_404 ).body
|
|
772
|
+
|
|
773
|
+
# force another 404 and grab the html body
|
|
774
|
+
force_404 = path + Digest::SHA1.hexdigest( rand( 9999999 ).to_s )
|
|
775
|
+
not_found2 = Typhoeus::Request.get( force_404 ).body
|
|
776
|
+
|
|
777
|
+
@_404[path]['file_rdiff'] = @_404[path]['file'].rdiff( not_found2 )
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
if( !@_404[path]['dir'] )
|
|
781
|
+
|
|
782
|
+
force_404 = path + Digest::SHA1.hexdigest( rand( 9999999 ).to_s ) + '/'
|
|
783
|
+
@_404[path]['dir'] = Typhoeus::Request.get( force_404 ).body
|
|
784
|
+
|
|
785
|
+
force_404 = path + Digest::SHA1.hexdigest( rand( 9999999 ).to_s ) + '/'
|
|
786
|
+
not_found2 = Typhoeus::Request.get( force_404 ).body
|
|
787
|
+
|
|
788
|
+
@_404[path]['dir_rdiff'] = @_404[path]['dir'].rdiff( not_found2 )
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
return @_404[path]['dir'].rdiff( res.body ) == @_404[path]['dir_rdiff'] ||
|
|
792
|
+
@_404[path]['file'].rdiff( res.body ) == @_404[path]['file_rdiff']
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
private
|
|
796
|
+
|
|
797
|
+
def redirect?( res )
|
|
798
|
+
if loc = res.headers_hash['Location']
|
|
799
|
+
return loc
|
|
800
|
+
end
|
|
801
|
+
return res
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def self.info
|
|
805
|
+
{ :name => 'HTTP' }
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
end
|
|
809
|
+
end
|