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,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