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.
Files changed (262) hide show
  1. data/ACKNOWLEDGMENTS.md +14 -0
  2. data/AUTHORS.md +6 -0
  3. data/CHANGELOG.md +162 -0
  4. data/CONTRIBUTORS.md +10 -0
  5. data/EXPLOITATION.md +429 -0
  6. data/HACKING.md +101 -0
  7. data/LICENSE.md +341 -0
  8. data/README.md +350 -0
  9. data/Rakefile +86 -0
  10. data/bin/arachni +22 -0
  11. data/bin/arachni_web +77 -0
  12. data/bin/arachni_xmlrpc +21 -0
  13. data/bin/arachni_xmlrpcd +82 -0
  14. data/bin/arachni_xmlrpcd_monitor +74 -0
  15. data/conf/README.webui.yaml.txt +44 -0
  16. data/conf/webui.yaml +11 -0
  17. data/external/metasploit/LICENSE +24 -0
  18. data/external/metasploit/modules/exploits/unix/webapp/arachni_exec.rb +142 -0
  19. data/external/metasploit/modules/exploits/unix/webapp/arachni_path_traversal.rb +113 -0
  20. data/external/metasploit/modules/exploits/unix/webapp/arachni_php_eval.rb +150 -0
  21. data/external/metasploit/modules/exploits/unix/webapp/arachni_php_include.rb +141 -0
  22. data/external/metasploit/modules/exploits/unix/webapp/arachni_sqlmap.rb +92 -0
  23. data/external/metasploit/plugins/arachni.rb +536 -0
  24. data/getoptslong.rb +241 -0
  25. data/lib/anemone.rb +2 -0
  26. data/lib/anemone/cookie_store.rb +35 -0
  27. data/lib/anemone/core.rb +371 -0
  28. data/lib/anemone/exceptions.rb +5 -0
  29. data/lib/anemone/http.rb +144 -0
  30. data/lib/anemone/page.rb +337 -0
  31. data/lib/anemone/page_store.rb +160 -0
  32. data/lib/anemone/storage.rb +34 -0
  33. data/lib/anemone/storage/base.rb +75 -0
  34. data/lib/anemone/storage/exceptions.rb +15 -0
  35. data/lib/anemone/storage/mongodb.rb +89 -0
  36. data/lib/anemone/storage/pstore.rb +50 -0
  37. data/lib/anemone/storage/redis.rb +90 -0
  38. data/lib/anemone/storage/tokyo_cabinet.rb +57 -0
  39. data/lib/anemone/tentacle.rb +40 -0
  40. data/lib/arachni.rb +16 -0
  41. data/lib/audit_store.rb +346 -0
  42. data/lib/component_manager.rb +293 -0
  43. data/lib/component_options.rb +395 -0
  44. data/lib/exceptions.rb +76 -0
  45. data/lib/framework.rb +637 -0
  46. data/lib/http.rb +809 -0
  47. data/lib/issue.rb +302 -0
  48. data/lib/module.rb +4 -0
  49. data/lib/module/auditor.rb +455 -0
  50. data/lib/module/base.rb +188 -0
  51. data/lib/module/element_db.rb +158 -0
  52. data/lib/module/key_filler.rb +87 -0
  53. data/lib/module/manager.rb +87 -0
  54. data/lib/module/output.rb +68 -0
  55. data/lib/module/trainer.rb +240 -0
  56. data/lib/module/utilities.rb +110 -0
  57. data/lib/options.rb +547 -0
  58. data/lib/parser.rb +2 -0
  59. data/lib/parser/auditable.rb +522 -0
  60. data/lib/parser/elements.rb +296 -0
  61. data/lib/parser/page.rb +149 -0
  62. data/lib/parser/parser.rb +717 -0
  63. data/lib/plugin.rb +4 -0
  64. data/lib/plugin/base.rb +110 -0
  65. data/lib/plugin/manager.rb +162 -0
  66. data/lib/report.rb +4 -0
  67. data/lib/report/base.rb +119 -0
  68. data/lib/report/manager.rb +92 -0
  69. data/lib/rpc/xml/client/base.rb +71 -0
  70. data/lib/rpc/xml/client/dispatcher.rb +49 -0
  71. data/lib/rpc/xml/client/instance.rb +88 -0
  72. data/lib/rpc/xml/server/base.rb +90 -0
  73. data/lib/rpc/xml/server/dispatcher.rb +357 -0
  74. data/lib/rpc/xml/server/framework.rb +206 -0
  75. data/lib/rpc/xml/server/instance.rb +191 -0
  76. data/lib/rpc/xml/server/module/manager.rb +46 -0
  77. data/lib/rpc/xml/server/options.rb +124 -0
  78. data/lib/rpc/xml/server/output.rb +299 -0
  79. data/lib/rpc/xml/server/plugin/manager.rb +58 -0
  80. data/lib/ruby.rb +5 -0
  81. data/lib/ruby/object.rb +32 -0
  82. data/lib/ruby/string.rb +74 -0
  83. data/lib/ruby/xmlrpc/server.rb +27 -0
  84. data/lib/spider.rb +200 -0
  85. data/lib/typhoeus/request.rb +91 -0
  86. data/lib/typhoeus/response.rb +34 -0
  87. data/lib/ui/cli/cli.rb +744 -0
  88. data/lib/ui/cli/output.rb +279 -0
  89. data/lib/ui/web/log.rb +82 -0
  90. data/lib/ui/web/output_stream.rb +94 -0
  91. data/lib/ui/web/report_manager.rb +222 -0
  92. data/lib/ui/web/server.rb +903 -0
  93. data/lib/ui/web/server/db/placeholder +0 -0
  94. data/lib/ui/web/server/public/banner.png +0 -0
  95. data/lib/ui/web/server/public/bodybg-small.png +0 -0
  96. data/lib/ui/web/server/public/bodybg.png +0 -0
  97. data/lib/ui/web/server/public/css/smoothness/images/pbar-ani.gif +0 -0
  98. data/lib/ui/web/server/public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  99. data/lib/ui/web/server/public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  100. data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  101. data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  102. data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  103. data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  104. data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  105. data/lib/ui/web/server/public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  106. data/lib/ui/web/server/public/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
  107. data/lib/ui/web/server/public/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  108. data/lib/ui/web/server/public/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
  109. data/lib/ui/web/server/public/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
  110. data/lib/ui/web/server/public/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  111. data/lib/ui/web/server/public/css/smoothness/jquery-ui-1.8.9.custom.css +573 -0
  112. data/lib/ui/web/server/public/favicon.ico +0 -0
  113. data/lib/ui/web/server/public/footer.jpg +0 -0
  114. data/lib/ui/web/server/public/icons/error.png +0 -0
  115. data/lib/ui/web/server/public/icons/info.png +0 -0
  116. data/lib/ui/web/server/public/icons/ok.png +0 -0
  117. data/lib/ui/web/server/public/icons/status.png +0 -0
  118. data/lib/ui/web/server/public/js/jquery-1.4.4.min.js +167 -0
  119. data/lib/ui/web/server/public/js/jquery-ui-1.8.9.custom.min.js +781 -0
  120. data/lib/ui/web/server/public/logo.png +0 -0
  121. data/lib/ui/web/server/public/nav-left.jpg +0 -0
  122. data/lib/ui/web/server/public/nav-right.jpg +0 -0
  123. data/lib/ui/web/server/public/nav-selected-left.jpg +0 -0
  124. data/lib/ui/web/server/public/nav-selected-right.jpg +0 -0
  125. data/lib/ui/web/server/public/reports/placeholder +1 -0
  126. data/lib/ui/web/server/public/sidebar-bottom.jpg +0 -0
  127. data/lib/ui/web/server/public/sidebar-h4.jpg +0 -0
  128. data/lib/ui/web/server/public/sidebar-top.jpg +0 -0
  129. data/lib/ui/web/server/public/spider.png +0 -0
  130. data/lib/ui/web/server/public/style.css +604 -0
  131. data/lib/ui/web/server/tmp/placeholder +0 -0
  132. data/lib/ui/web/server/views/dispatcher.erb +85 -0
  133. data/lib/ui/web/server/views/dispatcher_error.erb +14 -0
  134. data/lib/ui/web/server/views/error.erb +1 -0
  135. data/lib/ui/web/server/views/flash.erb +18 -0
  136. data/lib/ui/web/server/views/home.erb +14 -0
  137. data/lib/ui/web/server/views/instance.erb +213 -0
  138. data/lib/ui/web/server/views/layout.erb +95 -0
  139. data/lib/ui/web/server/views/log.erb +40 -0
  140. data/lib/ui/web/server/views/modules.erb +71 -0
  141. data/lib/ui/web/server/views/options.erb +23 -0
  142. data/lib/ui/web/server/views/output_results.erb +51 -0
  143. data/lib/ui/web/server/views/plugins.erb +42 -0
  144. data/lib/ui/web/server/views/report_formats.erb +30 -0
  145. data/lib/ui/web/server/views/reports.erb +55 -0
  146. data/lib/ui/web/server/views/settings.erb +120 -0
  147. data/lib/ui/web/server/views/welcome.erb +38 -0
  148. data/lib/ui/xmlrpc/dispatcher_monitor.rb +204 -0
  149. data/lib/ui/xmlrpc/xmlrpc.rb +843 -0
  150. data/logs/placeholder +0 -0
  151. data/metamodules/autothrottle.rb +74 -0
  152. data/metamodules/timeout_notice.rb +118 -0
  153. data/metamodules/uniformity.rb +98 -0
  154. data/modules/audit/code_injection.rb +136 -0
  155. data/modules/audit/code_injection_timing.rb +115 -0
  156. data/modules/audit/code_injection_timing/payloads.txt +4 -0
  157. data/modules/audit/csrf.rb +301 -0
  158. data/modules/audit/ldapi.rb +103 -0
  159. data/modules/audit/ldapi/errors.txt +26 -0
  160. data/modules/audit/os_cmd_injection.rb +103 -0
  161. data/modules/audit/os_cmd_injection/payloads.txt +2 -0
  162. data/modules/audit/os_cmd_injection_timing.rb +104 -0
  163. data/modules/audit/os_cmd_injection_timing/payloads.txt +3 -0
  164. data/modules/audit/path_traversal.rb +141 -0
  165. data/modules/audit/response_splitting.rb +105 -0
  166. data/modules/audit/rfi.rb +193 -0
  167. data/modules/audit/sqli.rb +120 -0
  168. data/modules/audit/sqli/regexp_ids.txt +90 -0
  169. data/modules/audit/sqli_blind_rdiff.rb +321 -0
  170. data/modules/audit/sqli_blind_timing.rb +103 -0
  171. data/modules/audit/sqli_blind_timing/payloads.txt +51 -0
  172. data/modules/audit/trainer.rb +89 -0
  173. data/modules/audit/unvalidated_redirect.rb +90 -0
  174. data/modules/audit/xpath.rb +104 -0
  175. data/modules/audit/xpath/errors.txt +26 -0
  176. data/modules/audit/xss.rb +99 -0
  177. data/modules/audit/xss_event.rb +134 -0
  178. data/modules/audit/xss_path.rb +125 -0
  179. data/modules/audit/xss_script_tag.rb +112 -0
  180. data/modules/audit/xss_tag.rb +112 -0
  181. data/modules/audit/xss_uri.rb +125 -0
  182. data/modules/recon/allowed_methods.rb +104 -0
  183. data/modules/recon/backdoors.rb +131 -0
  184. data/modules/recon/backdoors/filenames.txt +16 -0
  185. data/modules/recon/backup_files.rb +177 -0
  186. data/modules/recon/backup_files/extensions.txt +28 -0
  187. data/modules/recon/common_directories.rb +138 -0
  188. data/modules/recon/common_directories/directories.txt +265 -0
  189. data/modules/recon/common_files.rb +138 -0
  190. data/modules/recon/common_files/filenames.txt +17 -0
  191. data/modules/recon/directory_listing.rb +171 -0
  192. data/modules/recon/grep/captcha.rb +62 -0
  193. data/modules/recon/grep/credit_card.rb +85 -0
  194. data/modules/recon/grep/cvs_svn_users.rb +73 -0
  195. data/modules/recon/grep/emails.rb +59 -0
  196. data/modules/recon/grep/html_objects.rb +53 -0
  197. data/modules/recon/grep/private_ip.rb +54 -0
  198. data/modules/recon/grep/ssn.rb +53 -0
  199. data/modules/recon/htaccess_limit.rb +82 -0
  200. data/modules/recon/http_put.rb +95 -0
  201. data/modules/recon/interesting_responses.rb +118 -0
  202. data/modules/recon/unencrypted_password_forms.rb +119 -0
  203. data/modules/recon/webdav.rb +126 -0
  204. data/modules/recon/xst.rb +107 -0
  205. data/path_extractors/anchors.rb +35 -0
  206. data/path_extractors/forms.rb +35 -0
  207. data/path_extractors/frames.rb +38 -0
  208. data/path_extractors/generic.rb +39 -0
  209. data/path_extractors/links.rb +35 -0
  210. data/path_extractors/meta_refresh.rb +39 -0
  211. data/path_extractors/scripts.rb +37 -0
  212. data/path_extractors/sitemap.rb +31 -0
  213. data/plugins/autologin.rb +137 -0
  214. data/plugins/content_types.rb +90 -0
  215. data/plugins/cookie_collector.rb +99 -0
  216. data/plugins/form_dicattack.rb +185 -0
  217. data/plugins/healthmap.rb +94 -0
  218. data/plugins/http_dicattack.rb +133 -0
  219. data/plugins/metamodules.rb +118 -0
  220. data/plugins/proxy.rb +248 -0
  221. data/plugins/proxy/server.rb +66 -0
  222. data/plugins/waf_detector.rb +184 -0
  223. data/profiles/comprehensive.afp +74 -0
  224. data/profiles/full.afp +75 -0
  225. data/reports/afr.rb +59 -0
  226. data/reports/ap.rb +55 -0
  227. data/reports/html.rb +179 -0
  228. data/reports/html/default.erb +967 -0
  229. data/reports/metareport.rb +139 -0
  230. data/reports/metareport/arachni_metareport.rb +174 -0
  231. data/reports/plugin_formatters/html/content_types.rb +82 -0
  232. data/reports/plugin_formatters/html/cookie_collector.rb +66 -0
  233. data/reports/plugin_formatters/html/form_dicattack.rb +54 -0
  234. data/reports/plugin_formatters/html/healthmap.rb +76 -0
  235. data/reports/plugin_formatters/html/http_dicattack.rb +54 -0
  236. data/reports/plugin_formatters/html/metaformatters/timeout_notice.rb +65 -0
  237. data/reports/plugin_formatters/html/metaformatters/uniformity.rb +71 -0
  238. data/reports/plugin_formatters/html/metamodules.rb +93 -0
  239. data/reports/plugin_formatters/html/waf_detector.rb +54 -0
  240. data/reports/plugin_formatters/stdout/content_types.rb +73 -0
  241. data/reports/plugin_formatters/stdout/cookie_collector.rb +61 -0
  242. data/reports/plugin_formatters/stdout/form_dicattack.rb +52 -0
  243. data/reports/plugin_formatters/stdout/healthmap.rb +72 -0
  244. data/reports/plugin_formatters/stdout/http_dicattack.rb +53 -0
  245. data/reports/plugin_formatters/stdout/metaformatters/timeout_notice.rb +55 -0
  246. data/reports/plugin_formatters/stdout/metaformatters/uniformity.rb +68 -0
  247. data/reports/plugin_formatters/stdout/metamodules.rb +89 -0
  248. data/reports/plugin_formatters/stdout/waf_detector.rb +48 -0
  249. data/reports/plugin_formatters/xml/content_types.rb +91 -0
  250. data/reports/plugin_formatters/xml/cookie_collector.rb +70 -0
  251. data/reports/plugin_formatters/xml/form_dicattack.rb +57 -0
  252. data/reports/plugin_formatters/xml/healthmap.rb +82 -0
  253. data/reports/plugin_formatters/xml/http_dicattack.rb +57 -0
  254. data/reports/plugin_formatters/xml/metaformatters/timeout_notice.rb +67 -0
  255. data/reports/plugin_formatters/xml/metaformatters/uniformity.rb +82 -0
  256. data/reports/plugin_formatters/xml/metamodules.rb +91 -0
  257. data/reports/plugin_formatters/xml/waf_detector.rb +58 -0
  258. data/reports/stdout.rb +182 -0
  259. data/reports/txt.rb +77 -0
  260. data/reports/xml.rb +231 -0
  261. data/reports/xml/buffer.rb +98 -0
  262. metadata +516 -0
@@ -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