arachni 0.2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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,222 @@
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 UI
13
+ module Web
14
+
15
+ #
16
+ #
17
+ # Provides nice little wrapper for the Arachni::Report::Manager while also handling<br/>
18
+ # conversions, storing etc.
19
+ #
20
+ # @author: Tasos "Zapotek" Laskos
21
+ # <tasos.laskos@gmail.com>
22
+ # <zapotek@segfault.gr>
23
+ # @version: 0.1
24
+ #
25
+ class ReportManager
26
+
27
+ FOLDERNAME = "reports"
28
+ EXTENSION = '.afr'
29
+
30
+ def initialize( opts, settings )
31
+ @opts = opts
32
+ @settings = settings
33
+ populate_available
34
+ end
35
+
36
+ #
37
+ # @return [String] save directory
38
+ #
39
+ def savedir
40
+ @settings.public + "/#{FOLDERNAME}/"
41
+ end
42
+
43
+ #
44
+ # @return [String] tmp directory for storage while converting
45
+ #
46
+ def tmpdir
47
+ @settings.tmp + '/'
48
+ end
49
+
50
+ #
51
+ # Saves the report to a file
52
+ #
53
+ # @param [String] report YAML serialized audistore object as returned by the Arachni XMLRPC server.
54
+ # Basically an 'afr' report as a string.
55
+ #
56
+ # @return [String] the path to the saved report
57
+ #
58
+ def save( report )
59
+ @settings.log.report_saved( {}, get_filename( report ) )
60
+ return save_to_file( report, report_to_path( report ) )
61
+ end
62
+
63
+ #
64
+ # Gets the path to a given report based on the contents of the report
65
+ #
66
+ # @param [String] report YAML serialized audistore object as returned by the Arachni XMLRPC server.
67
+ # Basically an 'afr' report as a string.
68
+ # @return [String]
69
+ #
70
+ def report_to_path( report )
71
+ savedir + File.basename( get_filename( report ) + EXTENSION )
72
+ end
73
+
74
+ #
75
+ # Checks whether the provided type is a usable report
76
+ #
77
+ # @param [String] type usually html,txt,xml etc
78
+ #
79
+ # @return [Bool]
80
+ #
81
+ def valid_class?( type )
82
+ classes[type] || false
83
+ end
84
+
85
+ #
86
+ # Returns the paths of all saved report files as an array
87
+ #
88
+ # @return [Array]
89
+ #
90
+ def all
91
+ Dir.glob( savedir + "*#{EXTENSION}" )
92
+ end
93
+
94
+ def delete_all
95
+ all.each {
96
+ |report|
97
+ delete( report )
98
+ }
99
+ end
100
+
101
+ def delete( report )
102
+ FileUtils.rm( savedir + File.basename( report, '.afr' ) + '.afr' )
103
+ end
104
+
105
+ #
106
+ # Generates a filename based on the contents of the report in the form of
107
+ # host:audit_date
108
+ #
109
+ # @param [String] report YAML serialized audistore object as returned by the Arachni XMLRPC server.
110
+ # Basically an 'afr' report as a string.
111
+ #
112
+ # @return [String] host:audit_date
113
+ #
114
+ def get_filename( report )
115
+ rep = YAML::load( report )
116
+ filename = "#{URI(rep.options['url']).host}:#{rep.start_datetime}"
117
+ end
118
+
119
+ #
120
+ # Returns a stored report as a <type> file. Basically a convertion/export method.
121
+ #
122
+ # @param [String] type html, txt, xml, etc
123
+ # @param [String] report_file path to the report
124
+ #
125
+ # @return [String] the converted report
126
+ #
127
+ def get( type, report_file )
128
+ return if !valid_class?( type )
129
+
130
+ location = savedir + report_file + EXTENSION
131
+ convert( type, File.read( location ) )
132
+ end
133
+
134
+ #
135
+ # Returns all available report types
136
+ #
137
+ # @return [Array]
138
+ #
139
+ def available
140
+ return @@available
141
+ end
142
+
143
+ #
144
+ # Returns all available report classes
145
+ #
146
+ # @return [Array]
147
+ #
148
+ def classes
149
+ @@available_rep_classes
150
+ end
151
+
152
+ private
153
+
154
+ def save_to_file( data, file )
155
+ f = File.new( file, 'w' )
156
+ f.write( data )
157
+ f.close
158
+
159
+ return f.path
160
+ end
161
+
162
+ def convert( type, report )
163
+
164
+ opts = {}
165
+ classes[type].info[:options].each {
166
+ |opt|
167
+ opts[opt.name] = opt.default if opt.default
168
+ }
169
+ opts['outfile'] = get_tmp_outfile_name( type, report )
170
+
171
+ classes[type].new( YAML::load( report ), opts ).run
172
+
173
+ content = File.read( opts['outfile'] )
174
+ FileUtils.rm( opts['outfile'] )
175
+ return content
176
+ end
177
+
178
+
179
+ def get_tmp_outfile_name( type, report )
180
+ tmpdir + get_filename( report ) + '.' + type
181
+ end
182
+
183
+ def has_outfile?( options )
184
+ options.each {
185
+ |opt|
186
+ return true if opt.name == 'outfile'
187
+ }
188
+
189
+ return false
190
+ end
191
+
192
+
193
+ def populate_available
194
+ @@available ||= []
195
+ return @@available if !@@available.empty?
196
+
197
+ @@available_rep_classes ||= {}
198
+ report_mgr = ::Arachni::Report::Manager.new( @opts )
199
+ report_mgr.available.each {
200
+ |avail|
201
+
202
+ next if !report_mgr[avail].info[:options]
203
+ if has_outfile?( report_mgr[avail].info[:options] )
204
+ @@available << {
205
+ 'name' => report_mgr[avail].info[:name],
206
+ 'rep_name' => avail,
207
+ 'description' => report_mgr[avail].info[:description],
208
+ 'version' => report_mgr[avail].info[:version],
209
+ 'author' => report_mgr[avail].info[:author]
210
+ }
211
+
212
+ @@available_rep_classes[avail] = report_mgr[avail]
213
+
214
+ end
215
+ }
216
+ return @@available
217
+ end
218
+
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,903 @@
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'
12
+ require 'webrick/https'
13
+ require 'openssl'
14
+
15
+ require 'sinatra/base'
16
+ require "rack/csrf"
17
+ require 'rack-flash'
18
+ require 'json'
19
+ require 'erb'
20
+ require 'yaml'
21
+ require 'cgi'
22
+ require 'fileutils'
23
+ require 'ap'
24
+
25
+
26
+ module Arachni
27
+ module UI
28
+
29
+ require Arachni::Options.instance.dir['lib'] + 'ui/cli/output'
30
+ require Arachni::Options.instance.dir['lib'] + 'framework'
31
+ require Arachni::Options.instance.dir['lib'] + 'rpc/xml/client/dispatcher'
32
+ require Arachni::Options.instance.dir['lib'] + 'rpc/xml/client/instance'
33
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/report_manager'
34
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/log'
35
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/output_stream'
36
+
37
+ #
38
+ #
39
+ # Provides a web user interface for the Arachni Framework using Sinatra.<br/>
40
+ #
41
+ # @author: Tasos "Zapotek" Laskos
42
+ # <tasos.laskos@gmail.com>
43
+ # <zapotek@segfault.gr>
44
+ # @version: 0.1-pre
45
+ #
46
+ # @see Arachni::RPC::XML::Client::Instance
47
+ # @see Arachni::RPC::XML::Client::Dispatcher
48
+ #
49
+ module Web
50
+
51
+ VERSION = '0.1-pre'
52
+
53
+ class Server < Sinatra::Base
54
+
55
+ configure do
56
+ use Rack::Flash
57
+ use Rack::Session::Cookie
58
+ use Rack::Csrf, :raise => true
59
+
60
+ @@conf = YAML::load_file( Arachni::Options.instance.dir['root'] + 'conf/webui.yaml' )
61
+ Arachni::Options.instance.ssl = @@conf['ssl']['client']['enable']
62
+ Arachni::Options.instance.ssl_pkey = @@conf['ssl']['client']['key']
63
+ Arachni::Options.instance.ssl_cert = @@conf['ssl']['client']['cert']
64
+ Arachni::Options.instance.ssl_ca = @@conf['ssl']['client']['ca']
65
+
66
+ end
67
+
68
+ helpers do
69
+
70
+ def report_count
71
+ settings.reports.all.size
72
+ end
73
+
74
+ def plugin_has_required_file_option?( options )
75
+ options.each {
76
+ |opt|
77
+ return true if opt['type'] == 'path' && opt['required']
78
+ }
79
+
80
+ return false
81
+ end
82
+
83
+ def format_redundants( rules )
84
+ return if !rules || !rules.is_a?( Array ) || rules.empty?
85
+
86
+ str = ''
87
+ rules.each {
88
+ |rule|
89
+ next if !rule['regexp'] || !rule['count']
90
+ str += rule['regexp'] + ':' + rule['count'] + "\r\n"
91
+ }
92
+ return str
93
+ end
94
+
95
+ def prep_description( str )
96
+ placeholder = '--' + rand( 1000 ).to_s + '--'
97
+ cstr = str.gsub( /^\s*$/xm, placeholder )
98
+ cstr.gsub!( /^\s*/xm, '' )
99
+ cstr.gsub!( placeholder, "\n" )
100
+ cstr.chomp
101
+ end
102
+
103
+ def escape( str )
104
+ CGI.escapeHTML( str )
105
+ end
106
+
107
+ def unescape( str )
108
+ CGI.unescapeHTML( str )
109
+ end
110
+
111
+ def selected_tab?( tab )
112
+ splits = env['PATH_INFO'].split( '/' )
113
+ ( splits.empty? && tab == '/' ) || splits[1] == tab
114
+ end
115
+
116
+ def csrf_token
117
+ Rack::Csrf.csrf_token( env )
118
+ end
119
+
120
+ def csrf_tag
121
+ Rack::Csrf.csrf_tag( env )
122
+ end
123
+
124
+ def modules
125
+ @@modules
126
+ end
127
+
128
+ def plugins
129
+ @@plugins
130
+ end
131
+
132
+ def proc_mem( rss )
133
+ # we assume a page size of 4096
134
+ (rss.to_i * 4096 / 1024 / 1024).to_s + 'MB'
135
+ end
136
+
137
+ def secs_to_hms( secs )
138
+ secs = secs.to_i
139
+ return [secs/3600, secs/60 % 60, secs % 60].map {
140
+ |t|
141
+ t.to_s.rjust( 2, '0' )
142
+ }.join(':')
143
+ end
144
+
145
+ end
146
+
147
+
148
+ dir = File.dirname( File.expand_path( __FILE__ ) )
149
+
150
+ set :views, "#{dir}/server/views"
151
+ set :public, "#{dir}/server/public"
152
+ set :tmp, "#{dir}/server/tmp"
153
+ set :db, "#{dir}/server/db"
154
+ set :static, true
155
+ set :environment, :development
156
+
157
+ #
158
+ # This will be used for the "owner" field of the helper instance
159
+ #
160
+ HELPER_OWNER = "WebUI helper"
161
+
162
+ set :log, Log.new( Arachni::Options.instance, settings )
163
+ set :reports, ReportManager.new( Arachni::Options.instance, settings )
164
+
165
+ enable :sessions
166
+
167
+ configure do
168
+ # shit's on!
169
+ settings.log.webui_started
170
+ end
171
+
172
+ def exception_jail( &block )
173
+ # begin
174
+ block.call
175
+ # rescue Errno::ECONNREFUSED => e
176
+ # erb :error, { :layout => true }, :error => 'Remote server has been shut down.'
177
+ # end
178
+ end
179
+
180
+ def show( page, layout = true )
181
+ if page == :dispatcher
182
+ ensure_dispatcher
183
+ erb :dispatcher, { :layout => true }, :stats => dispatcher_stats
184
+ else
185
+ erb page.to_sym, { :layout => layout }
186
+ end
187
+ end
188
+
189
+ def welcomed?
190
+ File.exist?( settings.db + '/welcomed' )
191
+ end
192
+
193
+ def welcomed!
194
+ File.new( settings.db + '/welcomed', 'w' ).close
195
+ end
196
+
197
+ def ensure_welcomed
198
+ return if welcomed?
199
+
200
+ welcomed!
201
+ redirect '/welcome'
202
+ end
203
+
204
+ #
205
+ # Provides an easy way to connect to an instance by port
206
+ #
207
+ # @param [Integer] port
208
+ #
209
+ def connect_to_instance( port )
210
+ prep_session
211
+
212
+ begin
213
+ return Arachni::RPC::XML::Client::Instance.new( options, port_to_url( port ) )
214
+ rescue Exception
215
+ raise "Instance on port #{port} has shutdown."
216
+ end
217
+ end
218
+
219
+ #
220
+ # Converts a port to a URL instance.
221
+ #
222
+ # @param [Integer] port
223
+ #
224
+ def port_to_url( port )
225
+ uri = URI( session[:dispatcher_url] )
226
+ uri.port = port.to_i
227
+ uri.to_s
228
+ end
229
+
230
+ #
231
+ # Provides easy access to the dispatcher and handles failure
232
+ #
233
+ def dispatcher
234
+ begin
235
+ @dispatcher ||= Arachni::RPC::XML::Client::Dispatcher.new( options, session[:dispatcher_url] )
236
+ rescue Exception => e
237
+ redirect '/dispatcher_error'
238
+ end
239
+ end
240
+
241
+ #
242
+ # Provides statistics about running jobs etc using the dispatcher
243
+ #
244
+ def dispatcher_stats
245
+ stats = dispatcher.stats
246
+ stats['running_jobs'].each {
247
+ |job|
248
+ begin
249
+ job['paused'] = connect_to_instance( job['port'] ).framework.paused?
250
+ rescue
251
+ end
252
+ }
253
+ return stats
254
+ end
255
+
256
+ def options
257
+ Arachni::Options.instance
258
+ end
259
+
260
+ #
261
+ # Similar to String.to_i but it returns the original object if String is not a number
262
+ #
263
+ def to_i( str )
264
+ return str if !str.is_a?( String )
265
+
266
+ if str.match( /\d+/ ).to_s.size == str.size
267
+ return str.to_i
268
+ else
269
+ return str
270
+ end
271
+ end
272
+
273
+ #
274
+ # Prepares form params to be used as options for XMLRPC transmission
275
+ #
276
+ # @param [Hash] params
277
+ #
278
+ # @return [Hash] normalized hash
279
+ #
280
+ def prep_opts( params )
281
+
282
+ need_to_split = [
283
+ 'exclude_cookies',
284
+ 'exclude',
285
+ 'include'
286
+ ]
287
+
288
+ cparams = {}
289
+ params.each_pair {
290
+ |name, value|
291
+
292
+ next if [ '_csrf', 'modules', 'plugins' ].include?( name ) || ( value.is_a?( String ) && value.empty?)
293
+
294
+ value = true if value == 'on'
295
+
296
+ if name == 'cookiejar'
297
+ cparams['cookies'] = Arachni::HTTP.parse_cookiejar( value[:tempfile] )
298
+ elsif need_to_split.include?( name ) && value.is_a?( String )
299
+ cparams[name] = value.split( "\r\n" )
300
+
301
+ elsif name == 'redundant'
302
+ cparams[name] = []
303
+ value.split( "\r\n" ).each {
304
+ |rule|
305
+ regexp, counter = rule.split( ':', 2 )
306
+ cparams[name] << {
307
+ 'regexp' => regexp,
308
+ 'count' => counter
309
+ }
310
+ }
311
+ else
312
+ cparams[name] = to_i( value )
313
+ end
314
+ }
315
+
316
+ if !cparams['audit_links'] && !cparams['audit_forms'] &&
317
+ !cparams['audit_cookies'] && !cparams['audit_headers']
318
+
319
+ cparams['audit_links'] = true
320
+ cparams['audit_forms'] = true
321
+ cparams['audit_cookies'] = true
322
+ end
323
+
324
+ return cparams
325
+ end
326
+
327
+ def escape_hash( hash )
328
+ hash.each_pair {
329
+ |k, v|
330
+ hash[k] = escape( hash[k] ) if hash[k].is_a?( String )
331
+ hash[k] = escape_hash( v ) if v.is_a? Hash
332
+ }
333
+
334
+ return hash
335
+ end
336
+
337
+ def unescape_hash( hash )
338
+ hash.each_pair {
339
+ |k, v|
340
+ hash[k] = unescape( hash[k] ) if hash[k].is_a?( String )
341
+ hash[k] = unescape_hash( v ) if v.is_a? Hash
342
+ }
343
+
344
+ return hash
345
+ end
346
+
347
+ def prep_modules( params )
348
+ return ['-'] if !params['modules']
349
+ mods = params['modules'].keys
350
+ return ['*'] if mods.empty?
351
+ return mods
352
+ end
353
+
354
+ def prep_plugins( params )
355
+ plugins = {}
356
+
357
+ return plugins if !params['plugins']
358
+ params['plugins'].keys.each {
359
+ |name|
360
+ plugins[name] = params['options'][name] || {}
361
+ }
362
+
363
+ return plugins
364
+ end
365
+
366
+ def helper_instance
367
+ begin
368
+ @@arachni ||= nil
369
+ if !@@arachni
370
+ instance = dispatcher.dispatch( HELPER_OWNER )
371
+ @@arachni = connect_to_instance( instance['port'] )
372
+ end
373
+ return @@arachni
374
+ rescue
375
+ redirect '/dispatcher/error'
376
+ end
377
+ end
378
+
379
+ def component_cache_filled?
380
+ begin
381
+ return @@modules.size + @@plugins.size
382
+ rescue
383
+ return false
384
+ end
385
+ end
386
+
387
+ def fill_component_cache
388
+
389
+ if !component_cache_filled?
390
+ do_shutdown = true
391
+ else
392
+ do_shutdown = false
393
+ end
394
+
395
+ @@modules ||= helper_instance.framework.lsmod.dup
396
+ @@plugins ||= helper_instance.framework.lsplug.dup
397
+
398
+ # shutdown the helper instance, we got what we wanted
399
+ helper_instance.service.shutdown! if do_shutdown
400
+ end
401
+
402
+ #
403
+ # Makes sure that all systems are go and populates the session with default values
404
+ #
405
+ def prep_session
406
+ session[:dispatcher_url] ||= 'http://localhost:7331'
407
+
408
+ ensure_dispatcher
409
+
410
+ session['opts'] ||= {}
411
+ session['opts']['settings'] ||= {
412
+ 'audit_links' => true,
413
+ 'audit_forms' => true,
414
+ 'audit_cookies' => true,
415
+ 'http_req_limit' => 20,
416
+ 'user_agent' => 'Arachni/' + Arachni::VERSION
417
+ }
418
+ session['opts']['modules'] ||= [ '*' ]
419
+ session['opts']['plugins'] ||= YAML::dump( {
420
+ 'content_types' => {},
421
+ 'healthmap' => {},
422
+ 'metamodules' => {}
423
+ } )
424
+
425
+
426
+ #
427
+ # Garbage collector, zombie killer. Reaps idle processes every 5 seconds.
428
+ #
429
+ @@reaper ||= Thread.new {
430
+ while( true )
431
+ shutdown_zombies
432
+ ::IO::select( nil, nil, nil, 5 )
433
+ end
434
+ }
435
+
436
+ end
437
+
438
+ #
439
+ # Makes sure that we have a dispatcher, if not it redirects the user to
440
+ # an appropriate error page.
441
+ #
442
+ # @return [Bool] true if alive, redirect if not
443
+ #
444
+ def ensure_dispatcher
445
+ begin
446
+ dispatcher.alive?
447
+ rescue Exception => e
448
+ redirect '/dispatcher/error'
449
+ end
450
+ end
451
+
452
+ #
453
+ # Saves the report, shuts down the instance and returns the content as HTML
454
+ # to be sent back to the user's browser.
455
+ #
456
+ # @param [Arachni::RPC::XML::Client::Instance] arachni
457
+ #
458
+ def save_shutdown_and_show( arachni )
459
+ report = save_and_shutdown( arachni )
460
+ settings.reports.get( 'html', File.basename( report, '.afr' ) )
461
+ end
462
+
463
+ #
464
+ # Saves the report and shuts down the instance
465
+ #
466
+ # @param [Arachni::RPC::XML::Client::Instance] arachni
467
+ #
468
+ def save_and_shutdown( arachni )
469
+ arachni.framework.clean_up!
470
+ report_path = settings.reports.save( arachni.framework.auditstore )
471
+ arachni.service.shutdown!
472
+ return report_path
473
+ end
474
+
475
+ #
476
+ # Kills all running instances
477
+ #
478
+ def shutdown_all
479
+ settings.log.dispatcher_global_shutdown( env )
480
+ dispatcher.stats['running_jobs'].each {
481
+ |job|
482
+ begin
483
+ save_and_shutdown( connect_to_instance( job['port'] ) )
484
+ rescue
485
+ begin
486
+ connect_to_instance( job['port'] ).service.shutdown!
487
+ rescue
488
+ settings.log.instance_fucker_wont_die( env, port_to_url( job['port'] ) )
489
+ next
490
+ end
491
+ end
492
+
493
+ settings.log.instance_shutdown( env, port_to_url( job['port'] ) )
494
+ }
495
+ end
496
+
497
+ #
498
+ # Kills all idle instances
499
+ #
500
+ # @return [Integer] the number of reaped instances
501
+ #
502
+ def shutdown_zombies
503
+ i = 0
504
+ dispatcher.stats['running_jobs'].each {
505
+ |job|
506
+
507
+ begin
508
+ arachni = connect_to_instance( job['port'] )
509
+
510
+ begin
511
+ if !arachni.framework.busy? && !job['owner'] != HELPER_OWNER
512
+ save_and_shutdown( arachni )
513
+ settings.log.webui_zombie_cleanup( env, port_to_url( job['port'] ) )
514
+ i+=1
515
+ end
516
+ rescue
517
+
518
+ end
519
+
520
+ rescue
521
+ end
522
+ }
523
+
524
+ return i
525
+ end
526
+
527
+ get "/" do
528
+ prep_session
529
+ ensure_welcomed
530
+ show :home
531
+ end
532
+
533
+ get "/welcome" do
534
+ erb :welcome, { :layout => true }
535
+ end
536
+
537
+ get "/dispatcher" do
538
+ show :dispatcher
539
+ end
540
+
541
+ #
542
+ # sets the dispatcher URL
543
+ #
544
+ post "/dispatcher" do
545
+
546
+ if !params['url'] || params['url'].empty?
547
+ flash[:err] = "URL cannot be empty."
548
+ show :dispatcher_error
549
+ else
550
+
551
+ session[:dispatcher_url] = params['url']
552
+ settings.log.dispatcher_selected( env, params['url'] )
553
+ begin
554
+ dispatcher.jobs
555
+ settings.log.dispatcher_verified( env, params['url'] )
556
+ redirect '/'
557
+ rescue
558
+ settings.log.dispatcher_error( env, params['url'] )
559
+ flash[:err] = "Couldn't find a dispatcher at \"#{escape( params['url'] )}\"."
560
+ show :dispatcher_error
561
+ end
562
+ end
563
+ end
564
+
565
+ #
566
+ # shuts down all instances
567
+ #
568
+ post "/dispatcher/shutdown" do
569
+ shutdown_all
570
+ redirect '/dispatcher'
571
+ end
572
+
573
+
574
+ get '/dispatcher/error' do
575
+ show :dispatcher_error
576
+ end
577
+
578
+ #
579
+ # starts a scan
580
+ #
581
+ post "/scan" do
582
+
583
+ valid = true
584
+ begin
585
+ URI.parse( params['url'] )
586
+ rescue
587
+ valid = false
588
+ end
589
+
590
+ if !params['url'] || params['url'].empty?
591
+ flash[:err] = "URL cannot be empty."
592
+ show :home
593
+ elsif !valid
594
+ flash[:err] = "Invalid URL."
595
+ show :home
596
+ else
597
+
598
+ instance = dispatcher.dispatch( params['url'] )
599
+ settings.log.instance_dispatched( env, port_to_url( instance['port'] ) )
600
+ settings.log.instance_owner_assigned( env, params['url'] )
601
+
602
+ arachni = connect_to_instance( instance['port'] )
603
+
604
+ session['opts']['settings']['url'] = params['url']
605
+
606
+ unescape_hash( session['opts'] )
607
+
608
+ session['opts']['settings']['audit_links'] = true if session['opts']['settings']['audit_links']
609
+ session['opts']['settings']['audit_forms'] = true if session['opts']['settings']['audit_forms']
610
+ session['opts']['settings']['audit_cookies'] = true if session['opts']['settings']['audit_cookies']
611
+ session['opts']['settings']['audit_headers'] = true if session['opts']['settings']['audit_headers']
612
+
613
+ opts = prep_opts( session['opts']['settings'] )
614
+ arachni.opts.set( opts )
615
+ arachni.modules.load( session['opts']['modules'] )
616
+ arachni.plugins.load( YAML::load( session['opts']['plugins'] ) )
617
+ arachni.framework.run
618
+
619
+ settings.log.scan_started( env, params['url'] )
620
+
621
+ redirect '/instance/' + instance['port'].to_s
622
+ end
623
+
624
+ end
625
+
626
+ get "/modules" do
627
+ fill_component_cache
628
+ prep_session
629
+ show :modules, true
630
+ end
631
+
632
+ #
633
+ # sets modules
634
+ #
635
+ post "/modules" do
636
+ session['opts']['modules'] = prep_modules( escape_hash( params ) )
637
+ flash.now[:ok] = "Modules updated."
638
+ show :modules, true
639
+ end
640
+
641
+ get "/plugins" do
642
+ fill_component_cache
643
+ prep_session
644
+ erb :plugins, { :layout => true }
645
+ end
646
+
647
+ #
648
+ # sets plugins
649
+ #
650
+ post "/plugins" do
651
+ session['opts']['plugins'] = YAML::dump( prep_plugins( escape_hash( params ) ) )
652
+ flash.now[:ok] = "Plugins updated."
653
+ show :plugins, true
654
+ end
655
+
656
+ get "/settings" do
657
+ prep_session
658
+ erb :settings, { :layout => true }
659
+ end
660
+
661
+ #
662
+ # sets general framework settings
663
+ #
664
+ post "/settings" do
665
+
666
+ if session['opts']['settings']['url']
667
+ url = session['opts']['settings']['url'].dup
668
+ end
669
+
670
+ session['opts']['settings'] = prep_opts( escape_hash( params ) )
671
+
672
+ if session['opts']['settings']['url']
673
+ session['opts']['settings']['url'] = url
674
+ end
675
+
676
+ flash.now[:ok] = "Settings updated."
677
+ show :settings, true
678
+ end
679
+
680
+ get "/instance/:port" do
681
+ begin
682
+ arachni = connect_to_instance( params[:port] )
683
+ erb :instance, { :layout => true }, :paused => arachni.framework.paused?, :shutdown => false
684
+ rescue
685
+ flash.now[:notice] = "Instance on port #{params[:port]} has been shutdown."
686
+ erb :instance, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
687
+ end
688
+
689
+ end
690
+
691
+ get "/instance/:port/output.json" do
692
+ content_type :json
693
+
694
+ begin
695
+ arachni = connect_to_instance( params[:port] )
696
+ if arachni.framework.busy?
697
+ { 'data' => OutputStream.new( arachni, 38 ).data }.to_json
698
+ else
699
+ settings.log.instance_shutdown( env, port_to_url( params[:port] ) )
700
+ {
701
+ 'report' => '"data:text/html;base64, ' +
702
+ Base64.encode64( save_shutdown_and_show( arachni ) ) + '"'
703
+ }.to_json
704
+ end
705
+ rescue Errno::ECONNREFUSED
706
+ { 'status' => 'finished', 'data' => "The server has been shut down." }.to_json
707
+ end
708
+ end
709
+
710
+ get "/instance/:port/output_results.json" do
711
+ content_type :json
712
+ begin
713
+ arachni = connect_to_instance( params[:port] )
714
+ if !arachni.framework.paused? && arachni.framework.busy?
715
+ out = erb( :output_results, { :layout => false }, :issues => YAML.load( arachni.framework.auditstore ).issues)
716
+ { 'data' => out }.to_json
717
+ end
718
+ rescue Errno::ECONNREFUSED
719
+ { 'data' => "The server has been shut down." }.to_json
720
+ end
721
+ end
722
+
723
+ get "/instance/:port/stats.json" do
724
+ content_type :json
725
+ begin
726
+ arachni = connect_to_instance( params[:port] )
727
+ stats = arachni.framework.stats
728
+ stats['current_page'] = escape( stats['current_page'] )
729
+ { 'refresh' => true, 'stats' => stats }.to_json
730
+ rescue Errno::ECONNREFUSED
731
+ { 'refresh' => false }.to_json
732
+ end
733
+ end
734
+
735
+
736
+ post "/*/:port/pause" do
737
+ arachni = connect_to_instance( params[:port] )
738
+
739
+ begin
740
+ arachni.framework.pause!
741
+ settings.log.instance_paused( env, port_to_url( params[:port] ) )
742
+
743
+ flash.now[:notice] = "Instance on port #{params[:port]} will pause as soon as the current page is audited."
744
+ erb params[:splat][0].to_sym, { :layout => true }, :paused => arachni.framework.paused?, :shutdown => false, :stats => dispatcher_stats
745
+ rescue
746
+ flash.now[:notice] = "Instance on port #{params[:port]} has been shutdown."
747
+ erb params[:splat][0].to_sym, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
748
+ end
749
+
750
+ end
751
+
752
+ post "/*/:port/resume" do
753
+ arachni = connect_to_instance( params[:port] )
754
+
755
+ begin
756
+ arachni.framework.resume!
757
+ settings.log.instance_resumed( env, port_to_url( params[:port] ) )
758
+
759
+ flash.now[:notice] = "Instance on port #{params[:port]} resumes."
760
+ erb params[:splat][0].to_sym, { :layout => true }, :paused => arachni.framework.paused?, :shutdown => false, :stats => dispatcher_stats
761
+ rescue
762
+ flash.now[:notice] = "Instance on port #{params[:port]} has been shutdown."
763
+ erb params[:splat][0].to_sym, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
764
+ end
765
+ end
766
+
767
+ post "/*/:port/shutdown" do
768
+ arachni = connect_to_instance( params[:port] )
769
+
770
+ begin
771
+ arachni.framework.busy?
772
+ settings.log.instance_shutdown( env, port_to_url( params[:port] ) )
773
+
774
+ begin
775
+ save_shutdown_and_show( arachni )
776
+ rescue
777
+ flash.now[:notice] = "Instance on port #{params[:port]} has been shutdown."
778
+ show params[:splat][0].to_sym
779
+ ensure
780
+ arachni.service.shutdown!
781
+ end
782
+ rescue
783
+ flash.now[:notice] = "Instance on port #{params[:port]} has already been shutdown."
784
+ erb params[:splat][0].to_sym, { :layout => true }, :shutdown => true, :stats => dispatcher_stats
785
+ end
786
+ end
787
+
788
+ get "/reports" do
789
+
790
+ reports = []
791
+ settings.reports.all.each {
792
+ |report|
793
+ name = File.basename( report, '.afr' )
794
+ host, date = name.split( ':', 2 )
795
+ reports << {
796
+ 'host' => host,
797
+ 'date' => date,
798
+ 'name' => name
799
+ }
800
+ }
801
+
802
+ erb :reports, { :layout => true }, :reports => reports,
803
+ :available => settings.reports.available
804
+ end
805
+
806
+ get '/reports/formats' do
807
+ erb :report_formats, { :layout => true }, :reports => settings.reports.available
808
+ end
809
+
810
+ post '/reports/delete' do
811
+ settings.reports.delete_all
812
+ settings.log.reports_deleted( env )
813
+
814
+ redirect '/reports'
815
+ end
816
+
817
+ post '/report/:name/delete' do
818
+ settings.reports.delete( params[:name] )
819
+ settings.log.report_deleted( env, params[:name] )
820
+
821
+ redirect '/reports'
822
+ end
823
+
824
+ get '/report/:name.:type' do
825
+ settings.log.report_converted( env, params[:name] + '.' + params[:type] )
826
+ content_type( params[:type], :default => 'application/octet-stream' )
827
+ settings.reports.get( params[:type], params[:name] )
828
+ end
829
+
830
+ get '/log' do
831
+ erb :log, { :layout => true }, :entries => settings.log.entry.all.reverse
832
+ end
833
+
834
+ # override run! using this patch: https://github.com/sinatra/sinatra/pull/132
835
+ def self.run!( options = {} )
836
+ set options
837
+
838
+ handler = detect_rack_handler
839
+ handler_name = handler.name.gsub( /.*::/, '' )
840
+
841
+ # handler specific options use the lower case handler name as hash key, if present
842
+ handler_opts = options[handler_name.downcase.to_sym] || {}
843
+
844
+ puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
845
+ "on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
846
+
847
+ handler.run self, handler_opts.merge( :Host => bind, :Port => port ) do |server|
848
+ [ :INT, :TERM ].each { |sig| trap( sig ) { quit!( server, handler_name ) } }
849
+
850
+ set :running, true
851
+ end
852
+ rescue Errno::EADDRINUSE => e
853
+ puts "== Someone is already performing on port #{port}!"
854
+ end
855
+
856
+ def self.prep_webrick
857
+ if @@conf['ssl']['server']['key']
858
+ pkey = ::OpenSSL::PKey::RSA.new( File.read( @@conf['ssl']['server']['key'] ) )
859
+ end
860
+
861
+ if @@conf['ssl']['server']['cert']
862
+ cert = ::OpenSSL::X509::Certificate.new( File.read( @@conf['ssl']['server']['cert'] ) )
863
+ end
864
+
865
+ if @@conf['ssl']['key'] || @@conf['ssl']['cert'] || @@conf['ssl']['ca']
866
+ verification = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
867
+ else
868
+ verification = ::OpenSSL::SSL::VERIFY_NONE
869
+ end
870
+
871
+ return {
872
+ :SSLEnable => @@conf['ssl']['server']['enable'] || false,
873
+ :SSLVerifyClient => verification,
874
+ :SSLCertName => [ [ "CN", Arachni::Options.instance.server || ::WEBrick::Utils::getservername ] ],
875
+ :SSLCertificate => cert,
876
+ :SSLPrivateKey => pkey,
877
+ :SSLCACertificateFile => @@conf['ssl']['server']['ca']
878
+ }
879
+ end
880
+
881
+ run! :host => Arachni::Options.instance.server || ::WEBrick::Utils::getservername,
882
+ :port => Arachni::Options.instance.rpc_port || 4567,
883
+ :server => %w[ webrick ],
884
+ :webrick => prep_webrick
885
+
886
+ at_exit do
887
+
888
+ settings.log.webui_shutdown
889
+
890
+ begin
891
+ # shutdown our helper instance
892
+ @@arachni ||= nil
893
+ @@arachni.service.shutdown! if @@arachni
894
+ rescue
895
+ end
896
+
897
+ end
898
+
899
+ end
900
+
901
+ end
902
+ end
903
+ end