arachni 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (348) hide show
  1. data/ACKNOWLEDGMENTS.md +1 -1
  2. data/CHANGELOG.md +146 -0
  3. data/CONTRIBUTORS.md +1 -0
  4. data/HACKING.md +3 -3
  5. data/README.md +81 -49
  6. data/Rakefile +11 -14
  7. data/bin/arachni +4 -8
  8. data/bin/arachni_rpc +17 -0
  9. data/bin/arachni_rpcd +18 -0
  10. data/bin/arachni_rpcd_monitor +18 -0
  11. data/bin/arachni_web +25 -48
  12. data/bin/arachni_web_autostart +3 -3
  13. data/conf/README.webui.yaml.txt +7 -21
  14. data/external/metasploit/plugins/arachni.rb +0 -7
  15. data/extras/modules/recon/raft_dirs.rb +108 -0
  16. data/extras/modules/recon/raft_dirs/raft-large-directories.txt +62290 -0
  17. data/extras/modules/recon/raft_files.rb +110 -0
  18. data/extras/modules/recon/raft_files/raft-large-files.txt +37037 -0
  19. data/extras/modules/recon/svn_digger_dirs.rb +108 -0
  20. data/extras/modules/recon/svn_digger_dirs/Licence.txt +674 -0
  21. data/extras/modules/recon/svn_digger_dirs/ReadMe-Arachni.txt +4 -0
  22. data/extras/modules/recon/svn_digger_dirs/ReadMe.txt +6 -0
  23. data/extras/modules/recon/svn_digger_dirs/all-dirs.txt +5960 -0
  24. data/extras/modules/recon/svn_digger_files.rb +114 -0
  25. data/extras/modules/recon/svn_digger_files/Licence.txt +674 -0
  26. data/extras/modules/recon/svn_digger_files/ReadMe-Arachni.txt +4 -0
  27. data/extras/modules/recon/svn_digger_files/ReadMe.txt +6 -0
  28. data/extras/modules/recon/svn_digger_files/all-extensionless.txt +25419 -0
  29. data/extras/modules/recon/svn_digger_files/all.txt +43135 -0
  30. data/lib/arachni.rb +2 -7
  31. data/lib/{audit_store.rb → arachni/audit_store.rb} +68 -60
  32. data/lib/{component_manager.rb → arachni/component_manager.rb} +8 -8
  33. data/lib/{component_options.rb → arachni/component_options.rb} +34 -4
  34. data/lib/{crypto → arachni/crypto}/rsa_aes_cbc.rb +1 -2
  35. data/lib/arachni/database.rb +4 -0
  36. data/lib/arachni/database/base.rb +125 -0
  37. data/lib/arachni/database/hash.rb +384 -0
  38. data/lib/arachni/database/queue.rb +93 -0
  39. data/lib/{exceptions.rb → arachni/exceptions.rb} +1 -1
  40. data/lib/arachni/framework.rb +899 -0
  41. data/lib/{http.rb → arachni/http.rb} +63 -166
  42. data/lib/{issue.rb → arachni/issue.rb} +46 -17
  43. data/lib/{mixins → arachni/mixins}/observable.rb +1 -1
  44. data/lib/arachni/mixins/progress_bar.rb +81 -0
  45. data/lib/arachni/mixins/terminal.rb +106 -0
  46. data/lib/{module.rb → arachni/module.rb} +0 -0
  47. data/lib/{module → arachni/module}/auditor.rb +250 -86
  48. data/lib/{module → arachni/module}/base.rb +8 -18
  49. data/lib/{module → arachni/module}/element_db.rb +10 -2
  50. data/lib/{module → arachni/module}/key_filler.rb +1 -1
  51. data/lib/arachni/module/manager.rb +145 -0
  52. data/lib/{module → arachni/module}/output.rb +6 -1
  53. data/lib/{module → arachni/module}/trainer.rb +48 -52
  54. data/lib/{module → arachni/module}/utilities.rb +66 -15
  55. data/lib/{nokogiri → arachni/nokogiri}/xml/node.rb +0 -0
  56. data/lib/arachni/options.rb +986 -0
  57. data/lib/{parser.rb → arachni/parser.rb} +0 -0
  58. data/lib/{parser → arachni/parser}/auditable.rb +111 -32
  59. data/lib/{parser → arachni/parser}/elements.rb +28 -20
  60. data/lib/{parser → arachni/parser}/page.rb +20 -3
  61. data/lib/{parser → arachni/parser}/parser.rb +100 -63
  62. data/lib/{plugin.rb → arachni/plugin.rb} +0 -0
  63. data/lib/{plugin → arachni/plugin}/base.rb +43 -6
  64. data/lib/{plugin → arachni/plugin}/manager.rb +40 -13
  65. data/lib/{report.rb → arachni/report.rb} +0 -0
  66. data/lib/{report → arachni/report}/base.rb +43 -2
  67. data/lib/{report → arachni/report}/manager.rb +7 -18
  68. data/lib/arachni/rpc/client/base.rb +42 -0
  69. data/lib/{rpc/xml → arachni/rpc}/client/dispatcher.rb +12 -13
  70. data/lib/arachni/rpc/client/instance.rb +62 -0
  71. data/lib/arachni/rpc/server/base.rb +51 -0
  72. data/lib/arachni/rpc/server/dispatcher.rb +438 -0
  73. data/lib/arachni/rpc/server/framework.rb +1163 -0
  74. data/lib/arachni/rpc/server/instance.rb +184 -0
  75. data/lib/{rpc/xml → arachni/rpc}/server/module/manager.rb +8 -5
  76. data/lib/arachni/rpc/server/node.rb +267 -0
  77. data/lib/{rpc/xml → arachni/rpc}/server/options.rb +6 -35
  78. data/lib/{rpc/xml → arachni/rpc}/server/output.rb +29 -3
  79. data/lib/{rpc/xml → arachni/rpc}/server/plugin/manager.rb +5 -6
  80. data/lib/{ruby.rb → arachni/ruby.rb} +1 -2
  81. data/lib/arachni/ruby/array.rb +31 -0
  82. data/lib/{ruby → arachni/ruby}/object.rb +1 -1
  83. data/lib/{ruby → arachni/ruby}/string.rb +1 -1
  84. data/lib/{spider.rb → arachni/spider.rb} +83 -110
  85. data/lib/arachni/typhoeus/hydra.rb +7 -0
  86. data/lib/{typhoeus → arachni/typhoeus}/request.rb +11 -9
  87. data/lib/{typhoeus → arachni/typhoeus}/response.rb +4 -0
  88. data/lib/{ui → arachni/ui}/cli/cli.rb +154 -84
  89. data/lib/{ui → arachni/ui}/cli/output.rb +57 -19
  90. data/lib/{ui/xmlrpc → arachni/ui/rpc}/dispatcher_monitor.rb +11 -10
  91. data/lib/{ui/xmlrpc/xmlrpc.rb → arachni/ui/rpc/rpc.rb} +102 -158
  92. data/lib/{ui → arachni/ui}/web/addon_manager.rb +23 -3
  93. data/lib/arachni/ui/web/addons/autodeploy.rb +207 -0
  94. data/lib/{ui → arachni/ui}/web/addons/autodeploy/lib/manager.rb +142 -35
  95. data/lib/arachni/ui/web/addons/autodeploy/views/index.erb +291 -0
  96. data/lib/{ui → arachni/ui}/web/addons/sample.rb +1 -1
  97. data/lib/{ui → arachni/ui}/web/addons/sample/views/index.erb +0 -0
  98. data/lib/{ui → arachni/ui}/web/addons/scheduler.rb +30 -22
  99. data/lib/{ui → arachni/ui}/web/addons/scheduler/views/index.erb +56 -22
  100. data/lib/{ui → arachni/ui}/web/addons/scheduler/views/options.erb +0 -0
  101. data/lib/arachni/ui/web/dispatcher_manager.rb +274 -0
  102. data/lib/arachni/ui/web/instance_manager.rb +69 -0
  103. data/lib/{ui → arachni/ui}/web/log.rb +1 -1
  104. data/lib/arachni/ui/web/output_stream.rb +54 -0
  105. data/lib/{ui → arachni/ui}/web/report_manager.rb +48 -54
  106. data/lib/{ui → arachni/ui}/web/scheduler.rb +42 -47
  107. data/lib/arachni/ui/web/server.rb +1197 -0
  108. data/lib/{ui → arachni/ui}/web/server/db/placeholder +0 -0
  109. data/lib/{ui → arachni/ui}/web/server/public/banner.png +0 -0
  110. data/lib/{ui → arachni/ui}/web/server/public/bodybg-small.png +0 -0
  111. data/lib/{ui → arachni/ui}/web/server/public/bodybg.png +0 -0
  112. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/pbar-ani.gif +0 -0
  113. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  114. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  115. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  116. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  117. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  118. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  119. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  120. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  121. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
  122. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  123. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
  124. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
  125. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  126. data/lib/{ui → arachni/ui}/web/server/public/css/smoothness/jquery-ui-1.8.9.custom.css +0 -0
  127. data/lib/{ui → arachni/ui}/web/server/public/favicon.ico +0 -0
  128. data/lib/{ui → arachni/ui}/web/server/public/footer.jpg +0 -0
  129. data/lib/{ui/web/server/public/icons/error.png → arachni/ui/web/server/public/icons/bad.png} +0 -0
  130. data/lib/arachni/ui/web/server/public/icons/error.png +0 -0
  131. data/lib/{ui → arachni/ui}/web/server/public/icons/info.png +0 -0
  132. data/lib/{ui → arachni/ui}/web/server/public/icons/ok.png +0 -0
  133. data/lib/{ui → arachni/ui}/web/server/public/icons/status.png +0 -0
  134. data/lib/{ui → arachni/ui}/web/server/public/js/jquery-1.4.4.min.js +0 -0
  135. data/lib/{ui → arachni/ui}/web/server/public/js/jquery-ui-1.8.9.custom.min.js +0 -0
  136. data/lib/{ui → arachni/ui}/web/server/public/js/jquery-ui-timepicker.js +0 -0
  137. data/lib/{ui → arachni/ui}/web/server/public/logo.png +0 -0
  138. data/lib/{ui → arachni/ui}/web/server/public/nav-left.jpg +0 -0
  139. data/lib/{ui → arachni/ui}/web/server/public/nav-right.jpg +0 -0
  140. data/lib/{ui → arachni/ui}/web/server/public/nav-selected-left.jpg +0 -0
  141. data/lib/{ui → arachni/ui}/web/server/public/nav-selected-right.jpg +0 -0
  142. data/lib/{ui → arachni/ui}/web/server/public/plugins/sample/style.css +0 -0
  143. data/lib/{ui/web/server/tmp → arachni/ui/web/server/public/reports}/placeholder +0 -0
  144. data/lib/{ui → arachni/ui}/web/server/public/sidebar-bottom.jpg +0 -0
  145. data/lib/{ui → arachni/ui}/web/server/public/sidebar-h4.jpg +0 -0
  146. data/lib/{ui → arachni/ui}/web/server/public/sidebar-top.jpg +0 -0
  147. data/lib/{ui → arachni/ui}/web/server/public/spider.png +0 -0
  148. data/lib/{ui → arachni/ui}/web/server/public/style.css +3 -2
  149. data/lib/arachni/ui/web/server/tmp/placeholder +0 -0
  150. data/lib/{ui → arachni/ui}/web/server/views/addon.erb +0 -0
  151. data/lib/{ui → arachni/ui}/web/server/views/addons.erb +0 -0
  152. data/lib/{ui → arachni/ui}/web/server/views/dispatcher_error.erb +0 -0
  153. data/lib/arachni/ui/web/server/views/dispatchers.erb +175 -0
  154. data/lib/arachni/ui/web/server/views/dispatchers_edit.erb +71 -0
  155. data/lib/arachni/ui/web/server/views/error.erb +22 -0
  156. data/lib/{ui → arachni/ui}/web/server/views/flash.erb +2 -2
  157. data/lib/arachni/ui/web/server/views/home.erb +60 -0
  158. data/lib/{ui → arachni/ui}/web/server/views/instance.erb +55 -75
  159. data/lib/arachni/ui/web/server/views/js/home.erb +32 -0
  160. data/lib/{ui → arachni/ui}/web/server/views/layout.erb +2 -2
  161. data/lib/{ui → arachni/ui}/web/server/views/log.erb +0 -0
  162. data/lib/arachni/ui/web/server/views/module.erb +30 -0
  163. data/lib/{ui → arachni/ui}/web/server/views/modules.erb +2 -22
  164. data/lib/{ui → arachni/ui}/web/server/views/options.erb +0 -0
  165. data/lib/{ui → arachni/ui}/web/server/views/output_results.erb +4 -4
  166. data/lib/{ui → arachni/ui}/web/server/views/plugins.erb +23 -12
  167. data/lib/{ui → arachni/ui}/web/server/views/report_formats.erb +1 -1
  168. data/lib/{ui → arachni/ui}/web/server/views/reports.erb +1 -1
  169. data/lib/{ui → arachni/ui}/web/server/views/settings.erb +59 -16
  170. data/lib/{ui → arachni/ui}/web/server/views/welcome.erb +3 -1
  171. data/lib/{ui → arachni/ui}/web/utilities.rb +8 -3
  172. data/lib/arachni/version.rb +16 -0
  173. data/modules/audit/code_injection.rb +11 -20
  174. data/modules/audit/code_injection_timing.rb +2 -6
  175. data/modules/audit/csrf.rb +8 -16
  176. data/modules/audit/ldapi.rb +5 -11
  177. data/modules/audit/os_cmd_injection.rb +5 -9
  178. data/modules/audit/os_cmd_injection_timing.rb +4 -8
  179. data/modules/audit/path_traversal.rb +7 -13
  180. data/modules/audit/response_splitting.rb +8 -21
  181. data/modules/audit/rfi.rb +6 -46
  182. data/modules/audit/sqli.rb +5 -11
  183. data/modules/audit/sqli/regexp_ids.txt +0 -6
  184. data/modules/audit/sqli_blind_rdiff.rb +5 -10
  185. data/modules/audit/sqli_blind_timing.rb +4 -9
  186. data/modules/audit/trainer.rb +6 -12
  187. data/modules/audit/unvalidated_redirect.rb +6 -17
  188. data/modules/audit/xpath.rb +5 -12
  189. data/modules/audit/xss.rb +37 -23
  190. data/modules/audit/xss_event.rb +5 -10
  191. data/modules/audit/xss_path.rb +47 -41
  192. data/modules/audit/xss_script_tag.rb +5 -10
  193. data/modules/audit/xss_tag.rb +5 -10
  194. data/modules/audit/xss_uri.rb +17 -89
  195. data/modules/recon/allowed_methods.rb +6 -15
  196. data/modules/recon/backdoors.rb +12 -52
  197. data/modules/recon/backup_files.rb +25 -88
  198. data/modules/recon/common_directories.rb +8 -54
  199. data/modules/recon/common_files.rb +7 -58
  200. data/modules/recon/directory_listing.rb +6 -15
  201. data/modules/recon/grep/captcha.rb +1 -1
  202. data/modules/recon/grep/credit_card.rb +62 -27
  203. data/modules/recon/grep/cvs_svn_users.rb +1 -1
  204. data/modules/recon/grep/emails.rb +1 -1
  205. data/modules/recon/grep/html_objects.rb +1 -1
  206. data/modules/recon/grep/private_ip.rb +1 -1
  207. data/modules/recon/grep/ssn.rb +9 -9
  208. data/modules/recon/htaccess_limit.rb +6 -14
  209. data/modules/recon/http_put.rb +7 -15
  210. data/modules/recon/interesting_responses.rb +7 -13
  211. data/modules/recon/mixed_resource.rb +100 -0
  212. data/modules/recon/unencrypted_password_forms.rb +8 -20
  213. data/modules/recon/webdav.rb +6 -16
  214. data/modules/recon/xst.rb +7 -13
  215. data/path_extractors/anchors.rb +1 -1
  216. data/path_extractors/forms.rb +1 -1
  217. data/path_extractors/frames.rb +1 -1
  218. data/path_extractors/generic.rb +47 -3
  219. data/path_extractors/links.rb +1 -1
  220. data/path_extractors/meta_refresh.rb +1 -1
  221. data/path_extractors/scripts.rb +3 -4
  222. data/path_extractors/sitemap.rb +1 -1
  223. data/plugins/autologin.rb +9 -18
  224. data/plugins/beep_notify.rb +51 -0
  225. data/plugins/cookie_collector.rb +12 -12
  226. data/plugins/defaults/autothrottle.rb +86 -0
  227. data/plugins/{content_types.rb → defaults/content_types.rb} +25 -19
  228. data/plugins/{healthmap.rb → defaults/healthmap.rb} +30 -18
  229. data/plugins/defaults/metamodules/remedies/discovery.rb +164 -0
  230. data/plugins/defaults/metamodules/remedies/manual_verification.rb +65 -0
  231. data/{metamodules/timeout_notice.rb → plugins/defaults/metamodules/remedies/timing_attacks.rb} +26 -22
  232. data/{metamodules → plugins/defaults/metamodules}/uniformity.rb +15 -14
  233. data/plugins/{profiler.rb → defaults/profiler.rb} +19 -30
  234. data/plugins/defaults/resolver.rb +55 -0
  235. data/plugins/email_notify.rb +108 -0
  236. data/plugins/form_dicattack.rb +8 -16
  237. data/plugins/http_dicattack.rb +4 -12
  238. data/plugins/libnotify.rb +86 -0
  239. data/plugins/proxy.rb +8 -17
  240. data/plugins/proxy/server.rb +3 -3
  241. data/plugins/rescan.rb +60 -0
  242. data/plugins/waf_detector.rb +5 -16
  243. data/profiles/full.afp +3 -30
  244. data/reports/afr.rb +2 -5
  245. data/reports/ap.rb +3 -1
  246. data/reports/html.rb +210 -68
  247. data/reports/html/default.erb +72 -1014
  248. data/reports/html/default/configuration.erb +126 -0
  249. data/reports/html/default/css/jquery-ui.css +570 -0
  250. data/reports/html/default/css/jquery.jqplot.min.css +1 -0
  251. data/reports/html/default/css/main.css +391 -0
  252. data/reports/html/default/issue.erb +189 -0
  253. data/reports/html/default/issues.erb +65 -0
  254. data/reports/html/default/js/charts.js +146 -0
  255. data/reports/html/default/js/helpers.js +95 -0
  256. data/reports/html/default/js/init.js +73 -0
  257. data/reports/html/default/js/lib/jqplot.barRenderer.min.js +57 -0
  258. data/reports/html/default/js/lib/jqplot.categoryAxisRenderer.min.js +57 -0
  259. data/reports/html/default/js/lib/jqplot.cursor.min.js +57 -0
  260. data/reports/html/default/js/lib/jqplot.pieRenderer.min.js +57 -0
  261. data/reports/html/default/js/lib/jqplot.pointLabels.min.js +57 -0
  262. data/reports/html/default/js/lib/jquery-ui.min.js +404 -0
  263. data/reports/html/default/js/lib/jquery.jqplot.min.js +57 -0
  264. data/reports/html/default/js/lib/jquery.min.js +167 -0
  265. data/reports/html/default/plugins.erb +22 -0
  266. data/reports/html/default/search.erb +8 -0
  267. data/reports/html/default/sitemap.erb +15 -0
  268. data/reports/html/default/summary.erb +68 -0
  269. data/reports/html/default/summary_issue.erb +19 -0
  270. data/reports/json.rb +51 -0
  271. data/reports/marshal.rb +49 -0
  272. data/reports/metareport.rb +4 -6
  273. data/reports/metareport/arachni_metareport.rb +1 -1
  274. data/reports/plugin_formatters/html/autologin.rb +30 -41
  275. data/reports/plugin_formatters/html/content_types.rb +1 -10
  276. data/reports/plugin_formatters/html/cookie_collector.rb +36 -44
  277. data/reports/plugin_formatters/html/discovery.rb +50 -0
  278. data/reports/plugin_formatters/html/form_dicattack.rb +24 -32
  279. data/reports/plugin_formatters/html/healthmap.rb +45 -54
  280. data/reports/plugin_formatters/html/http_dicattack.rb +24 -32
  281. data/reports/plugin_formatters/html/profiler.rb +17 -48
  282. data/reports/plugin_formatters/html/profiler/template.erb +6 -99
  283. data/reports/plugin_formatters/html/resolver.rb +63 -0
  284. data/reports/plugin_formatters/html/{metaformatters/timeout_notice.rb → timing_attacks.rb} +7 -19
  285. data/reports/plugin_formatters/html/{metaformatters/uniformity.rb → uniformity.rb} +5 -17
  286. data/reports/plugin_formatters/html/waf_detector.rb +24 -32
  287. data/reports/plugin_formatters/stdout/autologin.rb +30 -35
  288. data/reports/plugin_formatters/stdout/content_types.rb +41 -46
  289. data/reports/plugin_formatters/stdout/cookie_collector.rb +33 -38
  290. data/reports/plugin_formatters/stdout/discovery.rb +47 -0
  291. data/reports/plugin_formatters/stdout/form_dicattack.rb +27 -32
  292. data/reports/plugin_formatters/stdout/healthmap.rb +47 -51
  293. data/reports/plugin_formatters/stdout/http_dicattack.rb +27 -32
  294. data/reports/plugin_formatters/stdout/metamodules.rb +48 -55
  295. data/reports/plugin_formatters/stdout/profiler.rb +60 -65
  296. data/reports/plugin_formatters/stdout/resolver.rb +45 -0
  297. data/reports/plugin_formatters/stdout/{metaformatters/timeout_notice.rb → timing_attacks.rb} +6 -14
  298. data/reports/plugin_formatters/stdout/{metaformatters/uniformity.rb → uniformity.rb} +6 -14
  299. data/reports/plugin_formatters/stdout/waf_detector.rb +23 -28
  300. data/reports/plugin_formatters/xml/autologin.rb +36 -41
  301. data/reports/plugin_formatters/xml/content_types.rb +47 -52
  302. data/reports/plugin_formatters/xml/cookie_collector.rb +39 -44
  303. data/reports/plugin_formatters/xml/discovery.rb +54 -0
  304. data/reports/plugin_formatters/xml/form_dicattack.rb +22 -27
  305. data/reports/plugin_formatters/xml/healthmap.rb +53 -58
  306. data/reports/plugin_formatters/xml/http_dicattack.rb +22 -27
  307. data/reports/plugin_formatters/xml/profiler.rb +61 -77
  308. data/reports/plugin_formatters/xml/resolver.rb +53 -0
  309. data/reports/plugin_formatters/xml/{metaformatters/timeout_notice.rb → timing_attacks.rb} +3 -15
  310. data/reports/plugin_formatters/xml/{metaformatters/uniformity.rb → uniformity.rb} +4 -14
  311. data/reports/plugin_formatters/xml/waf_detector.rb +23 -28
  312. data/reports/stdout.rb +1 -1
  313. data/reports/txt.rb +2 -5
  314. data/reports/xml.rb +2 -5
  315. data/reports/xml/buffer.rb +6 -2
  316. data/reports/yaml.rb +49 -0
  317. metadata +419 -278
  318. data/bin/arachni_xmlrpc +0 -21
  319. data/bin/arachni_xmlrpcd +0 -82
  320. data/bin/arachni_xmlrpcd_monitor +0 -74
  321. data/getoptslong.rb +0 -242
  322. data/lib/anemone.rb +0 -2
  323. data/lib/framework.rb +0 -673
  324. data/lib/module/manager.rb +0 -111
  325. data/lib/options.rb +0 -547
  326. data/lib/rpc/xml/client/base.rb +0 -76
  327. data/lib/rpc/xml/client/instance.rb +0 -88
  328. data/lib/rpc/xml/server/base.rb +0 -112
  329. data/lib/rpc/xml/server/dispatcher.rb +0 -386
  330. data/lib/rpc/xml/server/framework.rb +0 -206
  331. data/lib/rpc/xml/server/instance.rb +0 -191
  332. data/lib/ruby/xmlrpc/server.rb +0 -27
  333. data/lib/ui/web/addons/autodeploy.rb +0 -172
  334. data/lib/ui/web/addons/autodeploy/views/index.erb +0 -124
  335. data/lib/ui/web/dispatcher_manager.rb +0 -165
  336. data/lib/ui/web/instance_manager.rb +0 -87
  337. data/lib/ui/web/output_stream.rb +0 -94
  338. data/lib/ui/web/server.rb +0 -925
  339. data/lib/ui/web/server/public/reports/placeholder +0 -1
  340. data/lib/ui/web/server/views/dispatchers.erb +0 -100
  341. data/lib/ui/web/server/views/dispatchers_edit.erb +0 -42
  342. data/lib/ui/web/server/views/error.erb +0 -1
  343. data/lib/ui/web/server/views/home.erb +0 -25
  344. data/metamodules/autothrottle.rb +0 -74
  345. data/plugins/metamodules.rb +0 -118
  346. data/profiles/comprehensive.afp +0 -74
  347. data/reports/plugin_formatters/html/metamodules.rb +0 -93
  348. data/reports/plugin_formatters/xml/metamodules.rb +0 -91
@@ -1,6 +1,6 @@
1
1
  =begin
2
2
  Arachni
3
- Copyright (c) 2010-2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
3
+ Copyright (c) 2010-2012 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
4
4
 
5
5
  This is free software; you can copy and distribute and modify
6
6
  this program under the term of the GPL v2.0 License
@@ -9,7 +9,7 @@
9
9
  =end
10
10
 
11
11
  require 'datamapper'
12
- require Arachni::Options.instance.dir['lib'] + 'rpc/xml/client/dispatcher'
12
+ require Arachni::Options.instance.dir['lib'] + 'rpc/client/dispatcher'
13
13
  require Arachni::Options.instance.dir['lib'] + 'ui/web/utilities'
14
14
 
15
15
  module Arachni
@@ -23,7 +23,7 @@ module Web
23
23
  # @author: Tasos "Zapotek" Laskos
24
24
  # <tasos.laskos@gmail.com>
25
25
  # <zapotek@segfault.gr>
26
- # @version: 0.1.1
26
+ # @version: 0.1.2
27
27
  #
28
28
  class Scheduler
29
29
 
@@ -57,12 +57,7 @@ class Scheduler
57
57
 
58
58
  Job.auto_upgrade!
59
59
 
60
- begin
61
- ticktock!
62
- rescue Exception => e
63
- ap e
64
- ap e.backtrace
65
- end
60
+ ticktock!
66
61
  end
67
62
 
68
63
  #
@@ -74,31 +69,40 @@ class Scheduler
74
69
  #
75
70
  # @return [String] URL of the laucnhed scanner instance
76
71
  #
77
- def run( job, env = nil, session = nil )
78
- instance = @settings.dispatchers.connect( job.dispatcher ).dispatch( job.url )
79
- instance_url = @settings.instances.port_to_url( instance['port'], job.dispatcher )
72
+ def run( job, env = nil, session = nil, &block )
73
+ raise( "This method requires a block!" ) if !block_given?
80
74
 
81
- env = {
82
- 'REMOTE_ADDR' => job.owner_addr,
83
- 'REMOTE_HOST' => job.owner_host
84
- } if env.nil?
75
+ @settings.dispatchers.connect( job.dispatcher ).dispatch( job.url ){
76
+ |instance|
85
77
 
86
- @settings.log.scheduler_instance_dispatched( env, instance_url )
87
- @settings.log.scheduler_instance_owner_assigned( env, job.url )
88
-
89
- arachni = @settings.instances.connect( instance_url, session, instance['token'] )
78
+ if instance.rpc_exception?
79
+ @settings.log.scheduler_instance_dispatch_failed( env )
80
+ next
81
+ end
90
82
 
91
- opts = YAML::load( job.opts )
83
+ env = {
84
+ 'REMOTE_ADDR' => job.owner_addr,
85
+ 'REMOTE_HOST' => job.owner_host
86
+ } if env.nil?
92
87
 
93
- arachni.opts.set( opts['settings'] )
94
- arachni.modules.load( opts['modules'] )
95
- arachni.plugins.load( opts['plugins'] )
88
+ @settings.log.scheduler_instance_dispatched( env, instance['url'] )
89
+ @settings.log.scheduler_instance_owner_assigned( env, job.url )
96
90
 
97
- arachni.framework.run
91
+ arachni = @settings.instances.connect( instance['url'], session, instance['token'] )
98
92
 
99
- @settings.log.scheduler_scan_started( env, job.url )
93
+ opts = YAML::load( job.opts )
100
94
 
101
- return instance_url
95
+ arachni.opts.set( opts['settings'] ){
96
+ arachni.plugins.load( opts['plugins'] ) {
97
+ arachni.modules.load( opts['modules'] ) {
98
+ arachni.framework.run{
99
+ @settings.log.scheduler_scan_started( env, job.url )
100
+ block.call( instance['url'] )
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
102
106
  end
103
107
 
104
108
  #
@@ -106,9 +110,12 @@ class Scheduler
106
110
  #
107
111
  # @param [Job] job
108
112
  #
109
- def run_and_destroy( job )
110
- run( job )
111
- job.destroy
113
+ def run_and_destroy( job, &block )
114
+ run( job ){
115
+ |url|
116
+ job.destroy
117
+ block.call( url ) if block_given?
118
+ }
112
119
  end
113
120
 
114
121
  #
@@ -140,24 +147,12 @@ class Scheduler
140
147
  private
141
148
 
142
149
  def ticktock!
143
- @reaper ||= Thread.new {
144
- while( true )
145
- jobs.each {
146
- |job|
147
-
148
- begin
149
- run_and_destroy( job ) if job.datetime <= Time.now
150
- rescue Exception => e
151
- ap e
152
- ap e.backtrace
153
- end
154
-
155
- }
156
-
157
- ::IO::select( nil, nil, nil, 5 )
158
- end
150
+ ::EM.add_periodic_timer( 1 ){
151
+ jobs.each {
152
+ |job|
153
+ run_and_destroy( job ) if job.datetime <= DateTime.now
154
+ }
159
155
  }
160
-
161
156
  end
162
157
 
163
158
  end
@@ -0,0 +1,1197 @@
1
+ =begin
2
+ Arachni
3
+ Copyright (c) 2010-2012 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 'eventmachine'
12
+ require 'em-synchrony'
13
+ require 'sinatra/async'
14
+ require 'sinatra/flash'
15
+ require 'securerandom'
16
+ require 'json'
17
+ require 'erb'
18
+ require 'cgi'
19
+ require 'fileutils'
20
+
21
+ #
22
+ # Monkey patch Sinatra::Async to support error handling.
23
+ #
24
+ # This is from their own repo, can't wait for them to push the gem though.
25
+ #
26
+ module Sinatra::Async
27
+ def aerror( &block )
28
+ define_method :aerror, &block
29
+ end
30
+
31
+ module Helpers
32
+ def async_handle_exception
33
+ yield
34
+ rescue ::Exception => boom
35
+ if respond_to? :aerror
36
+ aerror boom
37
+ elsif settings.show_exceptions?
38
+ printer = Sinatra::ShowExceptions.new( proc{ raise boom } )
39
+ s, h, b = printer.call( request.env )
40
+ response.status = s
41
+ response.headers.replace( h )
42
+ response.body = b
43
+ else
44
+ body( handle_exception!( boom ) )
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ #
51
+ # Monkey patch Rack's cookie management to fix a nil error
52
+ #
53
+ # @see https://github.com/rack/rack/pull/304
54
+ #
55
+ class Rack::Session::Cookie
56
+ def unpacked_cookie_data(env)
57
+ env["rack.session.unpacked_cookie_data"] ||= begin
58
+ request = Rack::Request.new(env)
59
+ session_data = request.cookies[@key]
60
+
61
+ if @secret && session_data
62
+ session_data, digest = session_data.split("--")
63
+ unless digest == generate_hmac(session_data, @secret)
64
+ # Clear the session data if secret doesn't match and old secret doesn't match
65
+ session_data = nil if (@old_secret.nil? || (digest != generate_hmac(session_data, @old_secret)))
66
+ end
67
+ end
68
+
69
+ coder.decode(session_data) || {}
70
+ end
71
+ end
72
+ end
73
+
74
+ module Arachni
75
+ module UI
76
+
77
+ require Arachni::Options.instance.dir['lib'] + 'ui/cli/output'
78
+ require Arachni::Options.instance.dir['lib'] + 'framework'
79
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/utilities'
80
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/report_manager'
81
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/dispatcher_manager'
82
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/instance_manager'
83
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/scheduler'
84
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/log'
85
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/output_stream'
86
+
87
+ require Arachni::Options.instance.dir['lib'] + 'ui/web/addon_manager'
88
+
89
+ #
90
+ #
91
+ # Provides a web user interface for the Arachni Framework using Sinatra.
92
+ #
93
+ # It's basically an RPC client for Dispatchers and Instances wearing a pretty frock.
94
+ #
95
+ # @author: Tasos "Zapotek" Laskos
96
+ # <tasos.laskos@gmail.com>
97
+ # <zapotek@segfault.gr>
98
+ # @version: 0.2
99
+ #
100
+ # @see Arachni::RPC::Client::Instance
101
+ # @see Arachni::RPC::Client::Dispatcher
102
+ #
103
+ module Web
104
+
105
+ VERSION = '0.3'
106
+
107
+ class Server < Sinatra::Base
108
+
109
+ register Sinatra::Flash
110
+ register Sinatra::Async
111
+
112
+ include Arachni::Module::Utilities
113
+ include Utilities
114
+
115
+ configure do
116
+ use Rack::Session::Cookie
117
+
118
+ opts = Arachni::Options.instance
119
+
120
+ if opts.webui_username && opts.webui_password
121
+ use Rack::Auth::Basic, "Arachni WebUI v" + Arachni::UI::Web::VERSION + " requires authentication." do |username, password|
122
+ [username, password] == [ opts.webui_username, opts.webui_password ]
123
+ end
124
+ end
125
+
126
+ @@conf = YAML::load_file( opts.dir['conf'] + 'webui.yaml' )
127
+ opts.ssl = @@conf['ssl']['client']['enable']
128
+ opts.ssl_pkey = @@conf['ssl']['client']['key']
129
+ opts.ssl_cert = @@conf['ssl']['client']['cert']
130
+ opts.ssl_ca = @@conf['ssl']['client']['ca']
131
+ end
132
+
133
+ helpers do
134
+
135
+ #
136
+ # Converts seconds to a (00:00:00) (hours:minutes:seconds) string
137
+ #
138
+ # @param [String,Float,Integer] seconds
139
+ #
140
+ # @return [String] hours:minutes:seconds
141
+ #
142
+ def secs_to_hms( secs )
143
+ secs = secs.to_i
144
+ return [secs/3600, secs/60 % 60, secs % 60].map {
145
+ |t|
146
+ t.to_s.rjust( 2, '0' )
147
+ }.join(':')
148
+ end
149
+
150
+ def title
151
+ main = 'Arachni - Web Application Security Scanner Framework'
152
+
153
+ sub = env['PATH_INFO'].split( '/' ).map {
154
+ |part|
155
+ normalize_section_name( part )
156
+ }.reject { |part| part.empty? }.join( ' &rarr; ' )
157
+
158
+ return sub.empty? ? main : sub + ' :: ' + main
159
+ end
160
+
161
+ def normalize_section_name( name )
162
+ name.gsub( '_', ' ' ).capitalize
163
+ end
164
+
165
+ def job_is_slave?( job )
166
+ job['helpers']['rank'] == 'slave'
167
+ end
168
+
169
+ def report_count
170
+ reports.all.size
171
+ end
172
+
173
+ def plugin_has_required_file_option?( options )
174
+ options.each {
175
+ |opt|
176
+ return true if opt['type'] == 'path' && opt['required']
177
+ }
178
+
179
+ return false
180
+ end
181
+
182
+ def format_redundants( rules )
183
+ return if !rules || !rules.is_a?( Array ) || rules.empty?
184
+
185
+ str = ''
186
+ rules.each {
187
+ |rule|
188
+ next if !rule['regexp'] || !rule['count']
189
+ str += rule['regexp'] + ':' + rule['count'] + "\r\n"
190
+ }
191
+ return str
192
+ end
193
+
194
+ def format_custom_headers( headers )
195
+ return if !headers || !headers.is_a?( Hash ) || headers.empty?
196
+
197
+ str = ''
198
+ headers.each_pair {
199
+ |name, val|
200
+ str += "#{name}=#{val}\r\n"
201
+ }
202
+
203
+ return str
204
+ end
205
+
206
+ def prep_description( str )
207
+ placeholder = '--' + rand( 1000 ).to_s + '--'
208
+ cstr = str.gsub( /^\s*$/xm, placeholder )
209
+ cstr.gsub!( /^\s*/xm, '' )
210
+ cstr.gsub!( placeholder, "\n" )
211
+ cstr.chomp
212
+ end
213
+
214
+ def selected_tab?( tab )
215
+ splits = env['PATH_INFO'].split( '/' )
216
+ ( splits.empty? && tab == '/' ) || splits[1] == tab
217
+ end
218
+
219
+ def csrf_token
220
+ # Rack::Csrf.csrf_token( env )
221
+ @@csrf_token ||= SecureRandom.base64( 32 )
222
+ end
223
+
224
+ def csrf_field
225
+ '_csrf'
226
+ end
227
+
228
+ def csrf_key
229
+ 'csrf.token'
230
+ end
231
+
232
+ def csrf_tag
233
+ # Rack::Csrf.csrf_tag( env )
234
+ %Q(<input type="hidden" name="#{csrf_field}" value="#{csrf_token}" />)
235
+ end
236
+
237
+ def modules
238
+ @@modules
239
+ end
240
+
241
+ def plugins
242
+ @@plugins.reverse
243
+ end
244
+
245
+ def proc_mem( rss )
246
+ # we assume a page size of 4096
247
+ rss.to_i * 4096 / 1024 / 1024
248
+ end
249
+
250
+ def secs_to_hms( secs )
251
+ secs = secs.to_i
252
+ return [secs/3600, secs/60 % 60, secs % 60].map {
253
+ |t|
254
+ t.to_s.rjust( 2, '0' )
255
+ }.join(':')
256
+ end
257
+
258
+ end
259
+
260
+
261
+ dir = File.dirname( File.expand_path( __FILE__ ) )
262
+
263
+ set :views, "#{dir}/server/views"
264
+ set :public_folder, "#{dir}/server/public"
265
+ set :tmp, "#{dir}/server/tmp"
266
+ set :db, "#{dir}/server/db"
267
+ set :static, true
268
+ set :environment, :development
269
+
270
+ #
271
+ # This will be used for the "owner" field of the helper instance
272
+ #
273
+ HELPER_OWNER = "WebUI helper"
274
+
275
+ enable :sessions
276
+
277
+ set :log, Log.new( Arachni::Options.instance, settings )
278
+ set :reports, ReportManager.new( Arachni::Options.instance, settings )
279
+ set :dispatchers, DispatcherManager.new( Arachni::Options.instance, settings )
280
+ set :instances, InstanceManager.new( Arachni::Options.instance, settings )
281
+ set :scheduler, Scheduler.new( Arachni::Options.instance, settings )
282
+ set :conf, @@conf
283
+ set :addons, AddonManager.new( Arachni::Options.instance, settings )
284
+
285
+ configure do
286
+ # shit's on!
287
+ log.webui_started
288
+ end
289
+
290
+ def async_redirect( location, opts = {} )
291
+ response.status = 302
292
+
293
+ if methods.include?( :current_addon ) && current_addon &&
294
+ location != '/dispatchers/edit'
295
+ location = current_addon.path_root + location
296
+ end
297
+
298
+ if ( flash = opts[:flash] ) && !flash.empty?
299
+ location += "?#{flash.keys[0]}=#{URI.encode( flash.values[0] )}"
300
+ end
301
+
302
+ response.headers['Location'] = location
303
+
304
+ body ''
305
+ end
306
+
307
+ def redirect( location, opts = {} )
308
+ if methods.include?( :current_addon ) && current_addon
309
+ location = current_addon.path_root + location
310
+ end
311
+
312
+ if ( flash = opts[:flash] ) && !flash.empty?
313
+ location += "?#{flash.keys[0]}=#{URI.encode( flash.values[0] )}"
314
+ end
315
+
316
+ super( location )
317
+ end
318
+
319
+
320
+ def addons
321
+ settings.addons
322
+ end
323
+
324
+ def log
325
+ settings.log
326
+ end
327
+
328
+ def reports
329
+ settings.reports
330
+ end
331
+
332
+ def dispatchers
333
+ settings.dispatchers
334
+ end
335
+
336
+ #
337
+ # Provides statistics about running jobs etc using the dispatcher
338
+ #
339
+ def dispatcher_stats
340
+ dispatchers.stats
341
+ end
342
+
343
+ def scheduler
344
+ settings.scheduler
345
+ end
346
+
347
+ def instances
348
+ settings.instances
349
+ end
350
+
351
+ def exception_jail( &block )
352
+ # begin
353
+ block.call
354
+ # rescue Errno::ECONNREFUSED => e
355
+ # erb :error, { :layout => true }, :error => 'Remote server has been shut down.'
356
+ # end
357
+ end
358
+
359
+ def show( page, layout = true )
360
+
361
+ case page
362
+ when :dispatchers
363
+ ensure_dispatcher
364
+ dispatcher.stats {
365
+ |stats|
366
+ erb :dispatchers
367
+ }
368
+
369
+ when :home
370
+ dispatchers.stats {
371
+ |stats|
372
+ body erb page, { :layout => true }, :stats => stats
373
+ }
374
+ else
375
+ erb page.to_sym, { :layout => layout }
376
+ end
377
+ end
378
+
379
+ def show_dispatcher_line( stats )
380
+
381
+ str = "#{escape( '@' + stats['node']['url'] )}" +
382
+ " - #{stats['running_jobs'].size} running scans, "
383
+
384
+ rss = 0
385
+ mem = 0
386
+ cpu = 0
387
+
388
+ stats['running_jobs'].each {
389
+ |job|
390
+ rss += proc_mem( job['proc']['rss'] ).to_i
391
+ mem += Float( job['proc']['pctmem'] ) if job['proc']['pctmem']
392
+ cpu += Float( job['proc']['pctcpu'] ) if job['proc']['pctcpu']
393
+ }
394
+ str += rss.to_s + 'MB RAM usage '
395
+ str += '(' + mem.to_s[0..4] + '%), '
396
+ str += cpu.to_s[0..4] + '% CPU usage'
397
+ end
398
+
399
+ def show_dispatcher_node_line( stats )
400
+ str = "Nickname: #{stats['node']['nickname']} - "
401
+ str += "Pipe ID: #{stats['node']['pipe_id']} - "
402
+ str += "Weight: #{stats['node']['weight']}"
403
+ end
404
+
405
+ def welcomed?
406
+ File.exist?( settings.db + '/welcomed' )
407
+ end
408
+
409
+ def welcomed!
410
+ File.new( settings.db + '/welcomed', 'w' ).close
411
+ end
412
+
413
+ def ensure_welcomed
414
+ return if welcomed?
415
+ async_redirect '/welcome'
416
+ end
417
+
418
+ def options
419
+ Arachni::Options.instance
420
+ end
421
+
422
+ #
423
+ # Similar to String.to_i but it returns the original object if String is not a number
424
+ #
425
+ def to_i( str )
426
+ return str if !str.is_a?( String )
427
+
428
+ if str.match( /\d+/ ).to_s.size == str.size
429
+ return str.to_i
430
+ else
431
+ return str
432
+ end
433
+ end
434
+
435
+ #
436
+ # Prepares form params to be used as options for RPC transmission
437
+ #
438
+ # @param [Hash] params
439
+ #
440
+ # @return [Hash] normalized hash
441
+ #
442
+ def prep_opts( params )
443
+
444
+ need_to_split = [
445
+ 'exclude_cookies',
446
+ 'exclude',
447
+ 'include'
448
+ ]
449
+
450
+ cparams = {}
451
+ params.each_pair {
452
+ |name, value|
453
+
454
+ next if [ '_csrf', 'modules', 'plugins' ].include?( name ) || ( value.is_a?( String ) && value.empty?)
455
+
456
+ value = true if value == 'on'
457
+
458
+ if name == 'cookiejar'
459
+ cparams['cookies'] = Arachni::HTTP.parse_cookiejar( value[:tempfile] )
460
+ elsif name == 'extend_paths'
461
+ cparams['extend_paths'] = Arachni::Options.instance.paths_from_file( value[:tempfile] )
462
+ elsif name == 'restrict_paths'
463
+ cparams['restrict_paths'] = Arachni::Options.instance.paths_from_file( value[:tempfile] )
464
+ elsif need_to_split.include?( name ) && value.is_a?( String )
465
+ cparams[name] = value.split( "\r\n" )
466
+
467
+ elsif name == 'redundant'
468
+ cparams[name] = []
469
+ value.split( "\r\n" ).each {
470
+ |rule|
471
+ regexp, counter = rule.split( ':', 2 )
472
+ cparams[name] << {
473
+ 'regexp' => regexp,
474
+ 'count' => counter
475
+ }
476
+ }
477
+ elsif name == 'custom_headers'
478
+ cparams[name] = {}
479
+ value.split( "\r\n" ).each {
480
+ |line|
481
+ header, val = line.to_s.split( /=/, 2 )
482
+ cparams[name][header] = val
483
+ }
484
+ else
485
+ cparams[name] = to_i( value )
486
+ end
487
+ }
488
+
489
+ if !cparams['audit_links'] && !cparams['audit_forms'] &&
490
+ !cparams['audit_cookies'] && !cparams['audit_headers']
491
+
492
+ cparams['audit_links'] = true
493
+ cparams['audit_forms'] = true
494
+ cparams['audit_cookies'] = true
495
+ end
496
+
497
+ return cparams
498
+ end
499
+
500
+ def prep_modules( params )
501
+ return ['-'] if !params['modules']
502
+ mods = params['modules'].keys
503
+ return ['*'] if mods.empty?
504
+ return mods
505
+ end
506
+
507
+ def prep_plugins( params )
508
+ plugins = {}
509
+
510
+ return plugins if !params['plugins']
511
+ params['plugins'].keys.each {
512
+ |name|
513
+ plugins[name] = params['options'][name] || {}
514
+ }
515
+
516
+ return plugins
517
+ end
518
+
519
+ def helper_instance( &block )
520
+ raise( "This method requires a block!" ) if !block_given?
521
+
522
+ dispatchers.first_alive {
523
+ |dispatcher|
524
+ if !dispatcher
525
+ async_redirect '/dispatchers/edit'
526
+ else
527
+ dispatchers.connect( dispatcher.url ).dispatch( HELPER_OWNER ){
528
+ |instance|
529
+
530
+ if instance.rpc_exception?
531
+ log.webui_helper_instance_connect_failed( env, url )
532
+ next
533
+ end
534
+
535
+ @@arachni = instances.connect( instance['url'], session, instance['token'] )
536
+ block.call( @@arachni )
537
+ }
538
+ end
539
+ }
540
+ end
541
+
542
+ def component_cache_filled?
543
+ begin
544
+ return @@modules.size + @@plugins.size
545
+ rescue
546
+ return false
547
+ end
548
+ end
549
+
550
+ def fill_component_cache( &block )
551
+ if !component_cache_filled?
552
+
553
+ helper_instance {
554
+ |inst|
555
+
556
+ if !inst
557
+ block.call
558
+ else
559
+
560
+ inst.framework.lsmod {
561
+ |mods|
562
+
563
+ @@modules = mods.map { |mod| hash_keys_to_str( mod ) }
564
+
565
+ inst.framework.lsplug {
566
+ |plugs|
567
+
568
+ @@plugins = plugs.map { |plug| hash_keys_to_str( plug ) }
569
+
570
+ # shutdown the helper instance, we got what we wanted
571
+ inst.service.shutdown!{
572
+ block.call
573
+ }
574
+ }
575
+ }
576
+ end
577
+ }
578
+
579
+ else
580
+ block.call
581
+ end
582
+ end
583
+
584
+ #
585
+ # Makes sure that all systems are go and populates the session with default values
586
+ #
587
+ def prep_session( skip_dispatcher = false )
588
+ session[:flash] ||= {}
589
+
590
+ ensure_dispatcher if !skip_dispatcher
591
+
592
+ session['opts'] ||= {}
593
+ session['opts']['settings'] ||= {
594
+ 'audit_links' => true,
595
+ 'audit_forms' => true,
596
+ 'audit_cookies' => true,
597
+ 'http_req_limit' => 20,
598
+ 'user_agent' => 'Arachni/' + Arachni::VERSION
599
+ }
600
+ session['opts']['modules'] ||= [ '*' ]
601
+
602
+
603
+ require Arachni::Options.instance.dir['lib'] + 'framework'
604
+ framework = Arachni::Framework.new( Arachni::Options.instance )
605
+ plugins = Arachni::Plugin::Manager.new( framework )
606
+
607
+ default_plugins = {}
608
+ plugins.parse( Arachni::Plugin::Manager::DEFAULT ).each {
609
+ |mod|
610
+ default_plugins[mod] = {}
611
+ }
612
+
613
+ session['opts']['plugins'] ||= YAML::dump( default_plugins )
614
+
615
+ #
616
+ # Garbage collector, zombie killer. Reaps idle processes every 60 seconds.
617
+ #
618
+ @@zombie_reaper ||=
619
+ ::EM.add_periodic_timer( 60 ){ ::EM.defer { shutdown_zombies } }
620
+ end
621
+
622
+ #
623
+ # Makes sure that we have a dispatcher, if not it redirects the user to
624
+ # an appropriate error page.
625
+ #
626
+ # @return [Bool] true if alive, redirect if not
627
+ #
628
+ def ensure_dispatcher
629
+ if dispatchers.all.empty?
630
+ async_redirect '/dispatchers/edit'
631
+ else
632
+ dispatchers.first_alive {
633
+ |dispatcher|
634
+ async_redirect '/dispatchers/edit' if !dispatcher
635
+ }
636
+ end
637
+ end
638
+
639
+ #
640
+ # Saves the report and shuts down the instance
641
+ #
642
+ # @param [Arachni::RPC::Client::Instance] arachni
643
+ #
644
+ def save_and_shutdown( url, &block )
645
+ instance = instances.connect( url, session )
646
+ instance.framework.clean_up!{
647
+ |res|
648
+
649
+ if !res.rpc_connection_error?
650
+ instance.framework.auditstore {
651
+ |auditstore|
652
+
653
+ if !auditstore.rpc_connection_error?
654
+ log.webui_save_and_shutdown_auditstore_success( env, url )
655
+ report_path = reports.save( auditstore )
656
+ instance.service.shutdown!{ block.call( report_path ) }
657
+ else
658
+ log.webui_save_and_shutdown_auditstore_failed( env, url )
659
+ block.call( auditstore )
660
+ end
661
+ }
662
+ else
663
+ log.webui_save_and_shutdown_clean_up_failed( env, url )
664
+ block.call( res )
665
+ end
666
+ }
667
+ end
668
+
669
+ #
670
+ # Kills all running instances
671
+ #
672
+ def shutdown_all( url, &block )
673
+ dispatchers.connect( url ).stats {
674
+ |stats|
675
+ log.dispatcher_global_shutdown( env, url )
676
+
677
+ stats['running_jobs'].each {
678
+ |instance|
679
+
680
+ next if instance['helpers']['rank'] == 'slave'
681
+
682
+ save_and_shutdown( instance['url'] ){
683
+ log.instance_shutdown( env, instance['url'] )
684
+ }
685
+ }
686
+ block.call
687
+ }
688
+ end
689
+
690
+ #
691
+ # Kills all idle instances
692
+ #
693
+ # @return [Integer] the number of reaped instances
694
+ #
695
+ def shutdown_zombies
696
+ dispatchers.jobs {
697
+ |jobs|
698
+ jobs.each {
699
+ |job|
700
+ next if job['helpers']['rank'] == 'slave' ||
701
+ job['owner'] == HELPER_OWNER
702
+
703
+ instances.connect( job['url'], session ).framework.busy? {
704
+ |busy|
705
+
706
+ if !busy.rpc_exception? && !busy
707
+ save_and_shutdown( job['url'] ){
708
+ log.webui_zombie_cleanup( env, job['url'] )
709
+ }
710
+ end
711
+ }
712
+ }
713
+ }
714
+ end
715
+
716
+ def show_error( title, message = '', backtrace = [] )
717
+ skip = [
718
+ 'rack.input',
719
+ 'rack.errors',
720
+ 'async.callback',
721
+ 'async.close',
722
+ 'rack.logger',
723
+ ]
724
+
725
+ err_env = {}
726
+ env.each {
727
+ |k, v|
728
+ next if skip.include?( k )
729
+ err_env[k] = v
730
+ }
731
+
732
+ err_env['rack.session.options'].delete( :coder )
733
+ err_env['rack.session.options'].delete( :secure_random )
734
+
735
+ err_env.merge!(
736
+ :title => title,
737
+ :message => message,
738
+ :backtrace => backtrace.join( "\n" )
739
+ )
740
+
741
+ erb :error, { :layout => true },
742
+ :title => escape( title ),
743
+ :message => escape( message ).gsub( "\n", '<br />' ),
744
+ :backtrace => escape( backtrace.join( "\n" ) ),
745
+ :env => escape( err_env.to_yaml )
746
+ end
747
+
748
+ not_found do
749
+ show_error( 'Could not find the requested path',
750
+ 'Please use the menus for navigation, it\'ll be easier on you...' )
751
+ end
752
+
753
+ before do
754
+ if %q{POST PUT DELETE}.include?( env['REQUEST_METHOD'] ) &&
755
+ csrf_token != params[csrf_field]
756
+ redirect '/csrf_error'
757
+ end
758
+ end
759
+
760
+ aerror do |e|
761
+ body show_error( e.class.to_s, e.message, e.backtrace )
762
+ end
763
+
764
+ aget '/csrf_error' do
765
+ body show_error( 'Invalid CSRF token',
766
+ "The CSRF token that accompanied your last request was invalid.\n" +
767
+ "Please go back and try again..."
768
+ )
769
+ end
770
+
771
+ aget "/" do
772
+ prep_session
773
+ ensure_welcomed
774
+ dispatchers.stats {
775
+ |stats|
776
+ body erb :home, { :layout => true }, :stats => stats
777
+ }
778
+ end
779
+
780
+ aget "/welcome" do
781
+ welcomed!
782
+ body erb :welcome, { :layout => true }
783
+ end
784
+
785
+ aget "/dispatchers" do
786
+ ensure_dispatcher
787
+ dispatchers.stats {
788
+ |stats|
789
+ body erb :dispatchers, { :layout => true }, :stats => stats
790
+ }
791
+ end
792
+
793
+ aget '/dispatchers/edit' do
794
+ dispatchers.all_with_liveness {
795
+ |dispatchers|
796
+ body erb :dispatchers_edit, { :layout => true }, :dispatchers => dispatchers
797
+ }
798
+ end
799
+
800
+ apost '/dispatchers/add' do
801
+ dispatchers.alive?( params[:url] ) {
802
+ |a|
803
+ if a
804
+ dispatchers.new( params[:url] )
805
+ async_redirect '/dispatchers/edit'
806
+ else
807
+ msg = "Couldn't find a dispatcher at \"#{escape( params['url'] )}\"."
808
+ async_redirect '/dispatchers/edit', :flash => { :err => msg }
809
+ end
810
+ }
811
+ end
812
+
813
+ apost '/dispatchers/:url/delete' do |url|
814
+ dispatchers.delete( url )
815
+ redirect '/dispatchers/edit'
816
+ end
817
+
818
+ aget '/dispatchers/:url/log.json' do |url|
819
+ content_type :json
820
+
821
+ begin
822
+ dispatchers.connect( url ).log {
823
+ |log|
824
+ json = { 'log' => escape( log ) }.to_json
825
+ body json
826
+ }
827
+ rescue Exception => e
828
+ json = { 'error' => e.to_s, 'backtrace' => e.backtrace.join( "\n" ), }.to_json
829
+ body json
830
+ end
831
+ end
832
+
833
+ #
834
+ # shuts down all instances
835
+ #
836
+ apost "/dispatchers/:url/shutdown_all" do |url|
837
+ shutdown_all( url ){
838
+ msg = 'All instances will be shut down shortly, the reports will be download and saved automatically.'
839
+ async_redirect '/dispatchers', :flash => { :notice => msg }
840
+ }
841
+ end
842
+
843
+ #
844
+ # starts a scan
845
+ #
846
+ apost "/scan" do
847
+ prep_session( true )
848
+
849
+ valid = true
850
+ begin
851
+ URI.parse( params['url'] )
852
+ rescue
853
+ valid = false
854
+ end
855
+
856
+ if !params['url'] || params['url'].empty?
857
+ async_redirect '/', :flash => { :err => "URL cannot be empty." }
858
+ elsif !valid
859
+ async_redirect '/', :flash => { :err => "Invalid URL." }
860
+ elsif !params['dispatcher'] || params['dispatcher'].empty?
861
+ async_redirect '/', :flash => { :err => "Please select a Dispatcher." }
862
+ else
863
+
864
+ session['opts']['settings']['url'] = params[:url]
865
+
866
+ unescape_hash( session['opts'] )
867
+ session['opts']['settings']['audit_links'] = true if session['opts']['settings']['audit_links']
868
+ session['opts']['settings']['audit_forms'] = true if session['opts']['settings']['audit_forms']
869
+ session['opts']['settings']['audit_cookies'] = true if session['opts']['settings']['audit_cookies']
870
+ session['opts']['settings']['audit_headers'] = true if session['opts']['settings']['audit_headers']
871
+
872
+ opts = {}
873
+ opts['settings'] = prep_opts( session['opts']['settings'] )
874
+ opts['settings'] = session['opts']['settings']
875
+
876
+ if params['high_performance']
877
+ opts['settings']['grid_mode'] = 'high_performance'
878
+ opts['settings']['min_pages_per_instance']=
879
+ params['min_pages_per_instance']
880
+
881
+ opts['settings']['max_slaves'] = params['max_slaves']
882
+ end
883
+
884
+ opts['plugins'] = YAML::load( session['opts']['plugins'] )
885
+ opts['modules'] = session['opts']['modules']
886
+
887
+ job = Scheduler::Job.new(
888
+ :dispatcher => params[:dispatcher],
889
+ :url => params[:url],
890
+ :opts => YAML.dump( opts ),
891
+ :owner_addr => env['REMOTE_ADDR'],
892
+ :owner_host => env['REMOTE_HOST'],
893
+ :created_at => Time.now
894
+ )
895
+
896
+ scheduler.run( job, env, session ){
897
+ |instance_url|
898
+ async_redirect '/instance/' + instance_url
899
+ }
900
+ end
901
+ end
902
+
903
+ aget "/modules" do
904
+ prep_session
905
+ fill_component_cache{
906
+ body show :modules, true
907
+ }
908
+ end
909
+
910
+ #
911
+ # sets modules
912
+ #
913
+ post "/modules" do
914
+ prep_session
915
+ session['opts']['modules'] = prep_modules( escape_hash( params ) )
916
+ flash.now[:ok] = "Modules updated."
917
+ show :modules, true
918
+ end
919
+
920
+ aget "/plugins" do
921
+ prep_session
922
+ fill_component_cache {
923
+ body erb :plugins, { :layout => true },
924
+ :session_options => YAML::load( session['opts']['plugins'] )
925
+ }
926
+ end
927
+
928
+ #
929
+ # sets plugins
930
+ #
931
+ post "/plugins" do
932
+ prep_session
933
+ session['opts']['plugins'] = YAML::dump( prep_plugins( escape_hash( params ) ) )
934
+ flash.now[:ok] = "Plugins updated."
935
+ erb :plugins, { :layout => true }, :session_options => YAML::load( session['opts']['plugins'] )
936
+ end
937
+
938
+ get "/settings" do
939
+ prep_session
940
+ erb :settings, { :layout => true }
941
+ end
942
+
943
+ #
944
+ # sets general framework settings
945
+ #
946
+ post "/settings" do
947
+ if session['opts']['settings']['url']
948
+ url = session['opts']['settings']['url'].dup
949
+ end
950
+
951
+ session['opts']['settings'] = prep_opts( escape_hash( params ) )
952
+
953
+ if session['opts']['settings']['url']
954
+ session['opts']['settings']['url'] = url
955
+ end
956
+
957
+ flash.now[:ok] = "Settings updated."
958
+ show :settings, true
959
+ end
960
+
961
+ aget "/instance/:url" do |url|
962
+ params['url'] = url
963
+ instances.connect( params[:url], session ).framework.paused? {
964
+ |paused|
965
+
966
+ if !paused.rpc_connection_error?
967
+ body erb :instance, { :layout => true }, :paused => paused,
968
+ :shutdown => false, :params => params
969
+ else
970
+ msg = "Instance at #{url} has been shutdown."
971
+ body erb :instance, { :layout => true }, :shutdown => true,
972
+ :flash => { :notice => msg }
973
+ end
974
+
975
+ }
976
+
977
+ end
978
+
979
+ aget "/instance/:url/output.json" do |url|
980
+ content_type :json
981
+
982
+ params['url'] = url
983
+
984
+ output = {
985
+ 'messages' => {},
986
+ 'issues' => {},
987
+ 'stats' => {}
988
+ }
989
+
990
+ instances.connect( params[:url], session ).framework.progress_data {
991
+ |prog|
992
+
993
+ if !prog.rpc_exception?
994
+
995
+ @@output_streams ||= {}
996
+ @@output_streams[params[:url]] = OutputStream.new( prog['messages'], 38 )
997
+ output['messages'] = { 'data' => @@output_streams[params[:url]].format }
998
+ output['issues'] = { 'data' => erb( :output_results, { :layout => false }, :issues => prog['issues'] ) }
999
+
1000
+ output['stats'] = prog['stats'].dup
1001
+
1002
+ output['stats']['current_pages'] = []
1003
+ if prog['stats']['current_pages']
1004
+ prog['stats']['current_pages'].each {
1005
+ |url|
1006
+ output['stats']['current_pages'] << escape( url )
1007
+ }
1008
+ end
1009
+
1010
+ output['stats']['instances'] = prog['instances'].dup
1011
+ else
1012
+ msg = "The instance has been shutdown."
1013
+ output['messages'] = { 'data' => msg }
1014
+ output['issues'] = { 'data' => msg }
1015
+ output['status'] = 'finished'
1016
+ end
1017
+ body output.to_json
1018
+ }
1019
+ end
1020
+
1021
+
1022
+ apost "/*/:url/pause" do |splat, url|
1023
+ params['splat'] = [ splat ]
1024
+ params['url'] = url
1025
+
1026
+ redir = '/' + splat + ( splat == 'instance' ? "/#{url}" : '' )
1027
+ instances.connect( params[:url], session ).framework.pause!{
1028
+ |paused|
1029
+ if !paused.rpc_connection_error?
1030
+ log.instance_paused( env, params[:url] )
1031
+ msg = "Instance at #{params[:url]} will pause as soon as the current page is audited."
1032
+ async_redirect redir, :flash => { :notice => msg }
1033
+ else
1034
+ msg = "Instance at #{params[:url]} has been shutdown."
1035
+ async_redirect redir, :flash => { :notice => msg }
1036
+ end
1037
+ }
1038
+ end
1039
+
1040
+ apost "/*/:url/resume" do |splat, url|
1041
+ params['splat'] = [ splat ]
1042
+ params['url'] = url
1043
+
1044
+ redir = '/' + splat + ( splat == 'instance' ? "/#{url}" : '' )
1045
+ instances.connect( params[:url], session ).framework.resume!{
1046
+ |res|
1047
+
1048
+ if !res.rpc_connection_error?
1049
+ log.instance_resumed( env, params[:url] )
1050
+
1051
+ msg = "Instance at #{params[:url]} resumes."
1052
+ async_redirect redir, :flash => { :ok => msg }
1053
+ else
1054
+ msg = "Instance at #{params[:url]} has been shutdown."
1055
+ async_redirect redir, :flash => { :notice => msg }
1056
+ end
1057
+ }
1058
+ end
1059
+
1060
+ apost "/*/:url/shutdown" do |splat, url|
1061
+ params['splat'] = [ splat ]
1062
+ params['url'] = url
1063
+
1064
+ redir = '/' + ( splat == 'instance' ? "reports" : splat )
1065
+ save_and_shutdown( params[:url] ){
1066
+ |res|
1067
+
1068
+ if !res.rpc_connection_error?
1069
+ log.instance_shutdown( env, params[:url] )
1070
+ msg = {
1071
+ :ok => "Instance at #{params[:url]} has been shutdown."
1072
+ }
1073
+ else
1074
+ redir = '/' + ( splat == 'instance' ? splat + '/' + url : splat )
1075
+ msg = {
1076
+ :err => "Instance at #{params[:url]} could not be shutdown at the moment."
1077
+ }
1078
+ end
1079
+
1080
+ async_redirect redir, :flash => { msg.keys[0] => msg.values[0] }
1081
+ }
1082
+
1083
+ end
1084
+
1085
+ aget "/reports" do
1086
+ body erb :reports, { :layout => true }, :reports => reports.all( :order => :datestamp.desc ),
1087
+ :available => reports.available
1088
+ end
1089
+
1090
+ aget '/reports/formats' do
1091
+ body erb :report_formats, { :layout => true }, :reports => reports.available
1092
+ end
1093
+
1094
+ apost '/reports/delete' do
1095
+ reports.delete_all
1096
+ log.reports_deleted( env )
1097
+
1098
+ async_redirect '/reports'
1099
+ end
1100
+
1101
+ apost '/report/:id/delete' do |id|
1102
+ reports.delete( id )
1103
+ log.report_deleted( env, id )
1104
+
1105
+ async_redirect '/reports'
1106
+ end
1107
+
1108
+ aget '/report/:id.:type' do |id, type|
1109
+ log.report_converted( env, id + '.' + type )
1110
+ content_type( type, :default => 'application/octet-stream' )
1111
+ body reports.get( type, id )
1112
+ end
1113
+
1114
+ aget '/log' do
1115
+ body erb :log, { :layout => true }, :entries => log.entry.all.reverse
1116
+ end
1117
+
1118
+ aget '/addons' do
1119
+ body erb :addons
1120
+ end
1121
+
1122
+ apost '/addons' do
1123
+ params['addons'] ||= {}
1124
+ addon_names = params['addons'].keys
1125
+
1126
+ addons.enable!( addon_names )
1127
+ body erb :addons
1128
+ end
1129
+
1130
+
1131
+ # override run! using this patch: https://github.com/sinatra/sinatra/pull/132
1132
+ def self.run!( options = {} )
1133
+ set options
1134
+
1135
+ handler = detect_rack_handler
1136
+ handler_name = handler.name.gsub( /.*::/, '' )
1137
+
1138
+ # handler specific options use the lower case handler name as hash key, if present
1139
+ handler_opts = options[handler_name.downcase.to_sym] || {}
1140
+
1141
+ puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
1142
+ "on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
1143
+
1144
+ handler.run self, handler_opts.merge( :Host => options[:host], :Port => options[:port] ) do
1145
+ |server|
1146
+ [ :INT, :TERM ].each { |sig| trap( sig ) { quit!( server, handler_name ) } }
1147
+
1148
+ set :running, true
1149
+ end
1150
+ rescue Errno::EADDRINUSE => e
1151
+ puts "== Someone is already performing on port #{port}!"
1152
+ end
1153
+
1154
+ def self.prep_thin
1155
+ if @@conf['ssl']['server']['key']
1156
+ pkey = ::OpenSSL::PKey::RSA.new( File.read( @@conf['ssl']['server']['key'] ) )
1157
+ end
1158
+
1159
+ if @@conf['ssl']['server']['cert']
1160
+ cert = ::OpenSSL::X509::Certificate.new( File.read( @@conf['ssl']['server']['cert'] ) )
1161
+ end
1162
+
1163
+ if @@conf['ssl']['key'] || @@conf['ssl']['cert'] || @@conf['ssl']['ca']
1164
+ verification = true
1165
+ end
1166
+
1167
+ return {
1168
+ :ssl => true,
1169
+ :ssl_verify => verification,
1170
+ :ssl_cert_file => cert,
1171
+ :ssl_key_file => pkey,
1172
+ }
1173
+ end
1174
+
1175
+ run! :host => Arachni::Options.instance.server || '0.0.0.0',
1176
+ :port => Arachni::Options.instance.rpc_port || 4567,
1177
+ :server => %w[ thin ],
1178
+ :thin => prep_thin
1179
+
1180
+ at_exit do
1181
+
1182
+ log.webui_shutdown
1183
+
1184
+ begin
1185
+ # shutdown our helper instance
1186
+ @@arachni ||= nil
1187
+ @@arachni.service.shutdown! if @@arachni
1188
+ rescue
1189
+ end
1190
+
1191
+ end
1192
+
1193
+ end
1194
+
1195
+ end
1196
+ end
1197
+ end