arachni 0.2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ACKNOWLEDGMENTS.md +14 -0
- data/AUTHORS.md +6 -0
- data/CHANGELOG.md +162 -0
- data/CONTRIBUTORS.md +10 -0
- data/EXPLOITATION.md +429 -0
- data/HACKING.md +101 -0
- data/LICENSE.md +341 -0
- data/README.md +350 -0
- data/Rakefile +86 -0
- data/bin/arachni +22 -0
- data/bin/arachni_web +77 -0
- data/bin/arachni_xmlrpc +21 -0
- data/bin/arachni_xmlrpcd +82 -0
- data/bin/arachni_xmlrpcd_monitor +74 -0
- data/conf/README.webui.yaml.txt +44 -0
- data/conf/webui.yaml +11 -0
- data/external/metasploit/LICENSE +24 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_exec.rb +142 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_path_traversal.rb +113 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_php_eval.rb +150 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_php_include.rb +141 -0
- data/external/metasploit/modules/exploits/unix/webapp/arachni_sqlmap.rb +92 -0
- data/external/metasploit/plugins/arachni.rb +536 -0
- data/getoptslong.rb +241 -0
- data/lib/anemone.rb +2 -0
- data/lib/anemone/cookie_store.rb +35 -0
- data/lib/anemone/core.rb +371 -0
- data/lib/anemone/exceptions.rb +5 -0
- data/lib/anemone/http.rb +144 -0
- data/lib/anemone/page.rb +337 -0
- data/lib/anemone/page_store.rb +160 -0
- data/lib/anemone/storage.rb +34 -0
- data/lib/anemone/storage/base.rb +75 -0
- data/lib/anemone/storage/exceptions.rb +15 -0
- data/lib/anemone/storage/mongodb.rb +89 -0
- data/lib/anemone/storage/pstore.rb +50 -0
- data/lib/anemone/storage/redis.rb +90 -0
- data/lib/anemone/storage/tokyo_cabinet.rb +57 -0
- data/lib/anemone/tentacle.rb +40 -0
- data/lib/arachni.rb +16 -0
- data/lib/audit_store.rb +346 -0
- data/lib/component_manager.rb +293 -0
- data/lib/component_options.rb +395 -0
- data/lib/exceptions.rb +76 -0
- data/lib/framework.rb +637 -0
- data/lib/http.rb +809 -0
- data/lib/issue.rb +302 -0
- data/lib/module.rb +4 -0
- data/lib/module/auditor.rb +455 -0
- data/lib/module/base.rb +188 -0
- data/lib/module/element_db.rb +158 -0
- data/lib/module/key_filler.rb +87 -0
- data/lib/module/manager.rb +87 -0
- data/lib/module/output.rb +68 -0
- data/lib/module/trainer.rb +240 -0
- data/lib/module/utilities.rb +110 -0
- data/lib/options.rb +547 -0
- data/lib/parser.rb +2 -0
- data/lib/parser/auditable.rb +522 -0
- data/lib/parser/elements.rb +296 -0
- data/lib/parser/page.rb +149 -0
- data/lib/parser/parser.rb +717 -0
- data/lib/plugin.rb +4 -0
- data/lib/plugin/base.rb +110 -0
- data/lib/plugin/manager.rb +162 -0
- data/lib/report.rb +4 -0
- data/lib/report/base.rb +119 -0
- data/lib/report/manager.rb +92 -0
- data/lib/rpc/xml/client/base.rb +71 -0
- data/lib/rpc/xml/client/dispatcher.rb +49 -0
- data/lib/rpc/xml/client/instance.rb +88 -0
- data/lib/rpc/xml/server/base.rb +90 -0
- data/lib/rpc/xml/server/dispatcher.rb +357 -0
- data/lib/rpc/xml/server/framework.rb +206 -0
- data/lib/rpc/xml/server/instance.rb +191 -0
- data/lib/rpc/xml/server/module/manager.rb +46 -0
- data/lib/rpc/xml/server/options.rb +124 -0
- data/lib/rpc/xml/server/output.rb +299 -0
- data/lib/rpc/xml/server/plugin/manager.rb +58 -0
- data/lib/ruby.rb +5 -0
- data/lib/ruby/object.rb +32 -0
- data/lib/ruby/string.rb +74 -0
- data/lib/ruby/xmlrpc/server.rb +27 -0
- data/lib/spider.rb +200 -0
- data/lib/typhoeus/request.rb +91 -0
- data/lib/typhoeus/response.rb +34 -0
- data/lib/ui/cli/cli.rb +744 -0
- data/lib/ui/cli/output.rb +279 -0
- data/lib/ui/web/log.rb +82 -0
- data/lib/ui/web/output_stream.rb +94 -0
- data/lib/ui/web/report_manager.rb +222 -0
- data/lib/ui/web/server.rb +903 -0
- data/lib/ui/web/server/db/placeholder +0 -0
- data/lib/ui/web/server/public/banner.png +0 -0
- data/lib/ui/web/server/public/bodybg-small.png +0 -0
- data/lib/ui/web/server/public/bodybg.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/pbar-ani.gif +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/lib/ui/web/server/public/css/smoothness/jquery-ui-1.8.9.custom.css +573 -0
- data/lib/ui/web/server/public/favicon.ico +0 -0
- data/lib/ui/web/server/public/footer.jpg +0 -0
- data/lib/ui/web/server/public/icons/error.png +0 -0
- data/lib/ui/web/server/public/icons/info.png +0 -0
- data/lib/ui/web/server/public/icons/ok.png +0 -0
- data/lib/ui/web/server/public/icons/status.png +0 -0
- data/lib/ui/web/server/public/js/jquery-1.4.4.min.js +167 -0
- data/lib/ui/web/server/public/js/jquery-ui-1.8.9.custom.min.js +781 -0
- data/lib/ui/web/server/public/logo.png +0 -0
- data/lib/ui/web/server/public/nav-left.jpg +0 -0
- data/lib/ui/web/server/public/nav-right.jpg +0 -0
- data/lib/ui/web/server/public/nav-selected-left.jpg +0 -0
- data/lib/ui/web/server/public/nav-selected-right.jpg +0 -0
- data/lib/ui/web/server/public/reports/placeholder +1 -0
- data/lib/ui/web/server/public/sidebar-bottom.jpg +0 -0
- data/lib/ui/web/server/public/sidebar-h4.jpg +0 -0
- data/lib/ui/web/server/public/sidebar-top.jpg +0 -0
- data/lib/ui/web/server/public/spider.png +0 -0
- data/lib/ui/web/server/public/style.css +604 -0
- data/lib/ui/web/server/tmp/placeholder +0 -0
- data/lib/ui/web/server/views/dispatcher.erb +85 -0
- data/lib/ui/web/server/views/dispatcher_error.erb +14 -0
- data/lib/ui/web/server/views/error.erb +1 -0
- data/lib/ui/web/server/views/flash.erb +18 -0
- data/lib/ui/web/server/views/home.erb +14 -0
- data/lib/ui/web/server/views/instance.erb +213 -0
- data/lib/ui/web/server/views/layout.erb +95 -0
- data/lib/ui/web/server/views/log.erb +40 -0
- data/lib/ui/web/server/views/modules.erb +71 -0
- data/lib/ui/web/server/views/options.erb +23 -0
- data/lib/ui/web/server/views/output_results.erb +51 -0
- data/lib/ui/web/server/views/plugins.erb +42 -0
- data/lib/ui/web/server/views/report_formats.erb +30 -0
- data/lib/ui/web/server/views/reports.erb +55 -0
- data/lib/ui/web/server/views/settings.erb +120 -0
- data/lib/ui/web/server/views/welcome.erb +38 -0
- data/lib/ui/xmlrpc/dispatcher_monitor.rb +204 -0
- data/lib/ui/xmlrpc/xmlrpc.rb +843 -0
- data/logs/placeholder +0 -0
- data/metamodules/autothrottle.rb +74 -0
- data/metamodules/timeout_notice.rb +118 -0
- data/metamodules/uniformity.rb +98 -0
- data/modules/audit/code_injection.rb +136 -0
- data/modules/audit/code_injection_timing.rb +115 -0
- data/modules/audit/code_injection_timing/payloads.txt +4 -0
- data/modules/audit/csrf.rb +301 -0
- data/modules/audit/ldapi.rb +103 -0
- data/modules/audit/ldapi/errors.txt +26 -0
- data/modules/audit/os_cmd_injection.rb +103 -0
- data/modules/audit/os_cmd_injection/payloads.txt +2 -0
- data/modules/audit/os_cmd_injection_timing.rb +104 -0
- data/modules/audit/os_cmd_injection_timing/payloads.txt +3 -0
- data/modules/audit/path_traversal.rb +141 -0
- data/modules/audit/response_splitting.rb +105 -0
- data/modules/audit/rfi.rb +193 -0
- data/modules/audit/sqli.rb +120 -0
- data/modules/audit/sqli/regexp_ids.txt +90 -0
- data/modules/audit/sqli_blind_rdiff.rb +321 -0
- data/modules/audit/sqli_blind_timing.rb +103 -0
- data/modules/audit/sqli_blind_timing/payloads.txt +51 -0
- data/modules/audit/trainer.rb +89 -0
- data/modules/audit/unvalidated_redirect.rb +90 -0
- data/modules/audit/xpath.rb +104 -0
- data/modules/audit/xpath/errors.txt +26 -0
- data/modules/audit/xss.rb +99 -0
- data/modules/audit/xss_event.rb +134 -0
- data/modules/audit/xss_path.rb +125 -0
- data/modules/audit/xss_script_tag.rb +112 -0
- data/modules/audit/xss_tag.rb +112 -0
- data/modules/audit/xss_uri.rb +125 -0
- data/modules/recon/allowed_methods.rb +104 -0
- data/modules/recon/backdoors.rb +131 -0
- data/modules/recon/backdoors/filenames.txt +16 -0
- data/modules/recon/backup_files.rb +177 -0
- data/modules/recon/backup_files/extensions.txt +28 -0
- data/modules/recon/common_directories.rb +138 -0
- data/modules/recon/common_directories/directories.txt +265 -0
- data/modules/recon/common_files.rb +138 -0
- data/modules/recon/common_files/filenames.txt +17 -0
- data/modules/recon/directory_listing.rb +171 -0
- data/modules/recon/grep/captcha.rb +62 -0
- data/modules/recon/grep/credit_card.rb +85 -0
- data/modules/recon/grep/cvs_svn_users.rb +73 -0
- data/modules/recon/grep/emails.rb +59 -0
- data/modules/recon/grep/html_objects.rb +53 -0
- data/modules/recon/grep/private_ip.rb +54 -0
- data/modules/recon/grep/ssn.rb +53 -0
- data/modules/recon/htaccess_limit.rb +82 -0
- data/modules/recon/http_put.rb +95 -0
- data/modules/recon/interesting_responses.rb +118 -0
- data/modules/recon/unencrypted_password_forms.rb +119 -0
- data/modules/recon/webdav.rb +126 -0
- data/modules/recon/xst.rb +107 -0
- data/path_extractors/anchors.rb +35 -0
- data/path_extractors/forms.rb +35 -0
- data/path_extractors/frames.rb +38 -0
- data/path_extractors/generic.rb +39 -0
- data/path_extractors/links.rb +35 -0
- data/path_extractors/meta_refresh.rb +39 -0
- data/path_extractors/scripts.rb +37 -0
- data/path_extractors/sitemap.rb +31 -0
- data/plugins/autologin.rb +137 -0
- data/plugins/content_types.rb +90 -0
- data/plugins/cookie_collector.rb +99 -0
- data/plugins/form_dicattack.rb +185 -0
- data/plugins/healthmap.rb +94 -0
- data/plugins/http_dicattack.rb +133 -0
- data/plugins/metamodules.rb +118 -0
- data/plugins/proxy.rb +248 -0
- data/plugins/proxy/server.rb +66 -0
- data/plugins/waf_detector.rb +184 -0
- data/profiles/comprehensive.afp +74 -0
- data/profiles/full.afp +75 -0
- data/reports/afr.rb +59 -0
- data/reports/ap.rb +55 -0
- data/reports/html.rb +179 -0
- data/reports/html/default.erb +967 -0
- data/reports/metareport.rb +139 -0
- data/reports/metareport/arachni_metareport.rb +174 -0
- data/reports/plugin_formatters/html/content_types.rb +82 -0
- data/reports/plugin_formatters/html/cookie_collector.rb +66 -0
- data/reports/plugin_formatters/html/form_dicattack.rb +54 -0
- data/reports/plugin_formatters/html/healthmap.rb +76 -0
- data/reports/plugin_formatters/html/http_dicattack.rb +54 -0
- data/reports/plugin_formatters/html/metaformatters/timeout_notice.rb +65 -0
- data/reports/plugin_formatters/html/metaformatters/uniformity.rb +71 -0
- data/reports/plugin_formatters/html/metamodules.rb +93 -0
- data/reports/plugin_formatters/html/waf_detector.rb +54 -0
- data/reports/plugin_formatters/stdout/content_types.rb +73 -0
- data/reports/plugin_formatters/stdout/cookie_collector.rb +61 -0
- data/reports/plugin_formatters/stdout/form_dicattack.rb +52 -0
- data/reports/plugin_formatters/stdout/healthmap.rb +72 -0
- data/reports/plugin_formatters/stdout/http_dicattack.rb +53 -0
- data/reports/plugin_formatters/stdout/metaformatters/timeout_notice.rb +55 -0
- data/reports/plugin_formatters/stdout/metaformatters/uniformity.rb +68 -0
- data/reports/plugin_formatters/stdout/metamodules.rb +89 -0
- data/reports/plugin_formatters/stdout/waf_detector.rb +48 -0
- data/reports/plugin_formatters/xml/content_types.rb +91 -0
- data/reports/plugin_formatters/xml/cookie_collector.rb +70 -0
- data/reports/plugin_formatters/xml/form_dicattack.rb +57 -0
- data/reports/plugin_formatters/xml/healthmap.rb +82 -0
- data/reports/plugin_formatters/xml/http_dicattack.rb +57 -0
- data/reports/plugin_formatters/xml/metaformatters/timeout_notice.rb +67 -0
- data/reports/plugin_formatters/xml/metaformatters/uniformity.rb +82 -0
- data/reports/plugin_formatters/xml/metamodules.rb +91 -0
- data/reports/plugin_formatters/xml/waf_detector.rb +58 -0
- data/reports/stdout.rb +182 -0
- data/reports/txt.rb +77 -0
- data/reports/xml.rb +231 -0
- data/reports/xml/buffer.rb +98 -0
- metadata +516 -0
|
@@ -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
|