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
data/lib/issue.rb
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
$Id$
|
|
3
|
+
|
|
4
|
+
Arachni
|
|
5
|
+
Copyright (c) 2010-2011 Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
|
|
6
|
+
|
|
7
|
+
This is free software; you can copy and distribute and modify
|
|
8
|
+
this program under the term of the GPL v2.0 License
|
|
9
|
+
(See LICENSE file for details)
|
|
10
|
+
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# Vulnerability class.
|
|
16
|
+
#
|
|
17
|
+
# It represents a detected vulnerability.
|
|
18
|
+
#
|
|
19
|
+
#
|
|
20
|
+
# @author: Tasos "Zapotek" Laskos
|
|
21
|
+
# <tasos.laskos@gmail.com>
|
|
22
|
+
# <zapotek@segfault.gr>
|
|
23
|
+
# @version: 0.1
|
|
24
|
+
#
|
|
25
|
+
module Arachni
|
|
26
|
+
|
|
27
|
+
class Issue
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Holds constants to describe the {Issue#severity} of a
|
|
31
|
+
# vulnerability.
|
|
32
|
+
#
|
|
33
|
+
module Severity
|
|
34
|
+
HIGH = 'High'
|
|
35
|
+
MEDIUM = 'Medium'
|
|
36
|
+
LOW = 'Low'
|
|
37
|
+
INFORMATIONAL = 'Informational'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# Holds constants to describe the {Issue#elem} of a
|
|
42
|
+
# vulnerability.
|
|
43
|
+
#
|
|
44
|
+
module Element
|
|
45
|
+
LINK = 'link'
|
|
46
|
+
FORM = 'form'
|
|
47
|
+
COOKIE = 'cookie'
|
|
48
|
+
HEADER = 'header'
|
|
49
|
+
BODY = 'body'
|
|
50
|
+
PATH = 'path'
|
|
51
|
+
SERVER = 'server'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# The name of the issue
|
|
56
|
+
#
|
|
57
|
+
# @return [String]
|
|
58
|
+
#
|
|
59
|
+
attr_accessor :name
|
|
60
|
+
|
|
61
|
+
#
|
|
62
|
+
# The module that detected the issue
|
|
63
|
+
#
|
|
64
|
+
# @return [String] the name of the module
|
|
65
|
+
#
|
|
66
|
+
attr_accessor :mod_name
|
|
67
|
+
|
|
68
|
+
#
|
|
69
|
+
# The vulnerable HTTP variable
|
|
70
|
+
#
|
|
71
|
+
# @return [String] the name of the http variable
|
|
72
|
+
#
|
|
73
|
+
attr_accessor :var
|
|
74
|
+
|
|
75
|
+
#
|
|
76
|
+
# The vulnerable URL
|
|
77
|
+
#
|
|
78
|
+
# @return [String]
|
|
79
|
+
#
|
|
80
|
+
attr_accessor :url
|
|
81
|
+
|
|
82
|
+
#
|
|
83
|
+
# The headers exchanhed during the attack
|
|
84
|
+
#
|
|
85
|
+
# @return [Hash<String, Hash>] request and reply headers
|
|
86
|
+
#
|
|
87
|
+
attr_accessor :headers
|
|
88
|
+
|
|
89
|
+
#
|
|
90
|
+
# The HTML response of the attack
|
|
91
|
+
#
|
|
92
|
+
# @return [String] the html response of the attack
|
|
93
|
+
#
|
|
94
|
+
attr_accessor :response
|
|
95
|
+
|
|
96
|
+
#
|
|
97
|
+
# The injected data that revealed the issue
|
|
98
|
+
#
|
|
99
|
+
# @return [String]
|
|
100
|
+
#
|
|
101
|
+
attr_accessor :injected
|
|
102
|
+
|
|
103
|
+
#
|
|
104
|
+
# The string that identified the issue
|
|
105
|
+
#
|
|
106
|
+
# @return [String]
|
|
107
|
+
#
|
|
108
|
+
attr_accessor :id
|
|
109
|
+
|
|
110
|
+
#
|
|
111
|
+
# The regexp that identified the issue
|
|
112
|
+
#
|
|
113
|
+
# @return [String]
|
|
114
|
+
#
|
|
115
|
+
attr_accessor :regexp
|
|
116
|
+
|
|
117
|
+
#
|
|
118
|
+
# The data that was matched by the regexp
|
|
119
|
+
#
|
|
120
|
+
# @return [String]
|
|
121
|
+
#
|
|
122
|
+
attr_accessor :regexp_match
|
|
123
|
+
|
|
124
|
+
#
|
|
125
|
+
# The vulnerable element, link, form or cookie
|
|
126
|
+
#
|
|
127
|
+
# @return [String]
|
|
128
|
+
#
|
|
129
|
+
attr_accessor :elem
|
|
130
|
+
|
|
131
|
+
#
|
|
132
|
+
# HTTP method
|
|
133
|
+
#
|
|
134
|
+
# @return [String]
|
|
135
|
+
#
|
|
136
|
+
attr_accessor :method
|
|
137
|
+
|
|
138
|
+
#
|
|
139
|
+
# The description of the issue
|
|
140
|
+
#
|
|
141
|
+
# @return [String]
|
|
142
|
+
#
|
|
143
|
+
attr_accessor :description
|
|
144
|
+
|
|
145
|
+
#
|
|
146
|
+
# References related to the issue
|
|
147
|
+
#
|
|
148
|
+
# @return [Hash]
|
|
149
|
+
#
|
|
150
|
+
attr_accessor :references
|
|
151
|
+
|
|
152
|
+
#
|
|
153
|
+
# The CWE ID number of the issue
|
|
154
|
+
#
|
|
155
|
+
# @return [String]
|
|
156
|
+
#
|
|
157
|
+
attr_accessor :cwe
|
|
158
|
+
|
|
159
|
+
#
|
|
160
|
+
# The CWE URL of the issue
|
|
161
|
+
#
|
|
162
|
+
# @return [String]
|
|
163
|
+
#
|
|
164
|
+
attr_accessor :cwe_url
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# To be assigned a constant form {Severity}
|
|
168
|
+
#
|
|
169
|
+
# @see Severity
|
|
170
|
+
#
|
|
171
|
+
# @return [String]
|
|
172
|
+
#
|
|
173
|
+
attr_accessor :severity
|
|
174
|
+
|
|
175
|
+
#
|
|
176
|
+
# The CVSS v2 score
|
|
177
|
+
#
|
|
178
|
+
# @return [String]
|
|
179
|
+
#
|
|
180
|
+
attr_accessor :cvssv2
|
|
181
|
+
|
|
182
|
+
#
|
|
183
|
+
# A brief text informing the user how to remedy the situation
|
|
184
|
+
#
|
|
185
|
+
# @return [String]
|
|
186
|
+
#
|
|
187
|
+
attr_accessor :remedy_guidance
|
|
188
|
+
|
|
189
|
+
#
|
|
190
|
+
# A code snipet showing the user how to remedy the situation
|
|
191
|
+
#
|
|
192
|
+
# @return [String]
|
|
193
|
+
#
|
|
194
|
+
attr_accessor :remedy_code
|
|
195
|
+
|
|
196
|
+
#
|
|
197
|
+
# Placeholder variable to be populated by {AuditStore#prepare_variations}
|
|
198
|
+
#
|
|
199
|
+
# @see AuditStore#prepare_variations
|
|
200
|
+
#
|
|
201
|
+
attr_accessor :variations
|
|
202
|
+
|
|
203
|
+
#
|
|
204
|
+
# Is manual verification required?
|
|
205
|
+
#
|
|
206
|
+
# @return [Bool]
|
|
207
|
+
#
|
|
208
|
+
attr_accessor :verification
|
|
209
|
+
|
|
210
|
+
#
|
|
211
|
+
# The Metasploit module that can exploit the vulnerability.
|
|
212
|
+
#
|
|
213
|
+
# ex. exploit/unix/webapp/php_include
|
|
214
|
+
#
|
|
215
|
+
# @return [String]
|
|
216
|
+
#
|
|
217
|
+
attr_accessor :metasploitable
|
|
218
|
+
|
|
219
|
+
attr_accessor :opts
|
|
220
|
+
|
|
221
|
+
attr_accessor :internal_modname
|
|
222
|
+
attr_accessor :tags
|
|
223
|
+
attr_accessor :_hash
|
|
224
|
+
|
|
225
|
+
#
|
|
226
|
+
# Sets up the instanse attributes
|
|
227
|
+
#
|
|
228
|
+
# @param Hash cofiguration hash
|
|
229
|
+
# Usually the returned data of a module's
|
|
230
|
+
# info() method for the references
|
|
231
|
+
# merged with a name=>value pair hash holding
|
|
232
|
+
# class attributes
|
|
233
|
+
#
|
|
234
|
+
def initialize( opts = {} )
|
|
235
|
+
|
|
236
|
+
@verification = false
|
|
237
|
+
|
|
238
|
+
opts.each {
|
|
239
|
+
|k, v|
|
|
240
|
+
begin
|
|
241
|
+
send( "#{k.to_s.downcase}=", v )
|
|
242
|
+
rescue Exception => e
|
|
243
|
+
end
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
opts[:issue].each {
|
|
247
|
+
|k, v|
|
|
248
|
+
begin
|
|
249
|
+
send( "#{k.to_s.downcase}=", v )
|
|
250
|
+
rescue Exception => e
|
|
251
|
+
end
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if( @cwe )
|
|
255
|
+
@cwe_url = "http://cwe.mitre.org/data/definitions/" + @cwe + ".html"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
@mod_name = opts[:name]
|
|
259
|
+
@references = opts[:references] || {}
|
|
260
|
+
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def regexp=( regexp )
|
|
264
|
+
return if !regexp
|
|
265
|
+
@regexp = regexp.to_s
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def opts=( hash )
|
|
269
|
+
return if !hash
|
|
270
|
+
hash[:regexp] = hash[:regexp].to_s
|
|
271
|
+
hash[:match] ||= false
|
|
272
|
+
@opts = hash.dup
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def each
|
|
276
|
+
self.instance_variables.each {
|
|
277
|
+
|var|
|
|
278
|
+
yield( { normalize_name( var ) => instance_variable_get( var ) } )
|
|
279
|
+
}
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def each_pair
|
|
283
|
+
self.instance_variables.each {
|
|
284
|
+
|var|
|
|
285
|
+
yield normalize_name( var ), instance_variable_get( var )
|
|
286
|
+
}
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def remove_instance_var( var )
|
|
290
|
+
remove_instance_variable( var )
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
private
|
|
294
|
+
|
|
295
|
+
def normalize_name( name )
|
|
296
|
+
name.to_s.gsub( /@/, '' )
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
end
|
data/lib/module.rb
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
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 Module
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
# Auditor module
|
|
16
|
+
#
|
|
17
|
+
# Included by {Module::Base}.<br/>
|
|
18
|
+
# Includes audit methods.
|
|
19
|
+
#
|
|
20
|
+
# @author: Tasos "Zapotek" Laskos
|
|
21
|
+
# <tasos.laskos@gmail.com>
|
|
22
|
+
# <zapotek@segfault.gr>
|
|
23
|
+
# @version: 0.2.2
|
|
24
|
+
#
|
|
25
|
+
module Auditor
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# Holds constant bitfields that describe the preferred formatting
|
|
29
|
+
# of injection strings.
|
|
30
|
+
#
|
|
31
|
+
module Format
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Leaves the injection string as is.
|
|
35
|
+
#
|
|
36
|
+
STRAIGHT = 1 << 0
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Apends the injection string to the default value of the input vector.<br/>
|
|
40
|
+
# (If no default value exists Arachni will choose one.)
|
|
41
|
+
#
|
|
42
|
+
APPEND = 1 << 1
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# Terminates the injection string with a null character.
|
|
46
|
+
#
|
|
47
|
+
NULL = 1 << 2
|
|
48
|
+
|
|
49
|
+
#
|
|
50
|
+
# Prefix the string with a ';', useful for command injection modules
|
|
51
|
+
#
|
|
52
|
+
SEMICOLON = 1 << 3
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
# Holds constants that describe the HTML elements to be audited.
|
|
57
|
+
#
|
|
58
|
+
module Element
|
|
59
|
+
LINK = Issue::Element::LINK
|
|
60
|
+
FORM = Issue::Element::FORM
|
|
61
|
+
COOKIE = Issue::Element::COOKIE
|
|
62
|
+
HEADER = Issue::Element::HEADER
|
|
63
|
+
BODY = Issue::Element::BODY
|
|
64
|
+
PATH = Issue::Element::PATH
|
|
65
|
+
SERVER = Issue::Element::SERVER
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
#
|
|
69
|
+
# Default audit options.
|
|
70
|
+
#
|
|
71
|
+
OPTIONS = {
|
|
72
|
+
|
|
73
|
+
#
|
|
74
|
+
# Elements to audit.
|
|
75
|
+
#
|
|
76
|
+
# Only required when calling {#audit}.<br/>
|
|
77
|
+
# If no elements have been passed to audit it will
|
|
78
|
+
# use the elements in {#self.info}.
|
|
79
|
+
#
|
|
80
|
+
:elements => [ Element::LINK, Element::FORM,
|
|
81
|
+
Element::COOKIE, Element::HEADER,
|
|
82
|
+
Issue::Element::BODY ],
|
|
83
|
+
|
|
84
|
+
#
|
|
85
|
+
# The regular expression to match against the response body.
|
|
86
|
+
#
|
|
87
|
+
:regexp => nil,
|
|
88
|
+
|
|
89
|
+
#
|
|
90
|
+
# Verify the matched string with this value.
|
|
91
|
+
#
|
|
92
|
+
:match => nil,
|
|
93
|
+
|
|
94
|
+
#
|
|
95
|
+
# Formatting of the injection strings.
|
|
96
|
+
#
|
|
97
|
+
# A new set of audit inputs will be generated
|
|
98
|
+
# for each value in the array.
|
|
99
|
+
#
|
|
100
|
+
# Values can be OR'ed bitfields of all available constants
|
|
101
|
+
# of {Auditor::Format}.
|
|
102
|
+
#
|
|
103
|
+
# @see Auditor::Format
|
|
104
|
+
#
|
|
105
|
+
:format => [ Format::STRAIGHT, Format::APPEND,
|
|
106
|
+
Format::NULL, Format::APPEND | Format::NULL ],
|
|
107
|
+
|
|
108
|
+
#
|
|
109
|
+
# If 'train' is set to true the HTTP response will be
|
|
110
|
+
# analyzed for new elements. <br/>
|
|
111
|
+
# Be carefull when enabling it, there'll be a performance penalty.
|
|
112
|
+
#
|
|
113
|
+
# When the Auditor submits a form with original or sample values
|
|
114
|
+
# this option will be overriden to true.
|
|
115
|
+
#
|
|
116
|
+
:train => false,
|
|
117
|
+
|
|
118
|
+
#
|
|
119
|
+
# Enable skipping of already audited inputs
|
|
120
|
+
#
|
|
121
|
+
:redundant => false,
|
|
122
|
+
|
|
123
|
+
#
|
|
124
|
+
# Make requests asynchronously
|
|
125
|
+
#
|
|
126
|
+
:async => true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#
|
|
130
|
+
# Matches the HTML in @page.html to an array of regular expressions
|
|
131
|
+
# and logs the results.
|
|
132
|
+
#
|
|
133
|
+
# @param [Array<Regexp>] regexps
|
|
134
|
+
# @param [String] string
|
|
135
|
+
# @param [Block] block block to verify matches before logging
|
|
136
|
+
# must return true/false
|
|
137
|
+
#
|
|
138
|
+
def match_and_log( regexps, string = @page.html, &block )
|
|
139
|
+
|
|
140
|
+
# make sure that we're working with an array
|
|
141
|
+
regexps = [regexps].flatten
|
|
142
|
+
|
|
143
|
+
elems = self.class.info[:elements]
|
|
144
|
+
elems = OPTIONS[:elements] if !elems || elems.empty?
|
|
145
|
+
|
|
146
|
+
regexps.each {
|
|
147
|
+
|regexp|
|
|
148
|
+
|
|
149
|
+
string.scan( regexp ).flatten.uniq.each {
|
|
150
|
+
|match|
|
|
151
|
+
|
|
152
|
+
next if !match
|
|
153
|
+
next if block && !block.call( match )
|
|
154
|
+
|
|
155
|
+
log(
|
|
156
|
+
:regexp => regexp,
|
|
157
|
+
:match => match,
|
|
158
|
+
:element => Issue::Element::BODY
|
|
159
|
+
)
|
|
160
|
+
} if elems.include? Issue::Element::BODY
|
|
161
|
+
|
|
162
|
+
next if string == @page.html
|
|
163
|
+
|
|
164
|
+
@page.response_headers.each {
|
|
165
|
+
|k,v|
|
|
166
|
+
next if !v
|
|
167
|
+
|
|
168
|
+
v.to_s.scan( regexp ).flatten.uniq.each {
|
|
169
|
+
|match|
|
|
170
|
+
|
|
171
|
+
next if !match
|
|
172
|
+
next if block && !block.call( match )
|
|
173
|
+
|
|
174
|
+
log(
|
|
175
|
+
:var => k,
|
|
176
|
+
:regexp => regexp,
|
|
177
|
+
:match => match,
|
|
178
|
+
:element => Issue::Element::HEADER
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
} if elems.include? Issue::Element::HEADER
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
#
|
|
187
|
+
# Logs a vulnerability based on a regular expression and it's matched string
|
|
188
|
+
#
|
|
189
|
+
# @param [Regexp] regexp
|
|
190
|
+
# @param [String] match
|
|
191
|
+
#
|
|
192
|
+
def log( opts, res = nil )
|
|
193
|
+
|
|
194
|
+
method = nil
|
|
195
|
+
|
|
196
|
+
request_headers = nil
|
|
197
|
+
response_headers = @page.response_headers
|
|
198
|
+
response = @page.html
|
|
199
|
+
url = @page.url
|
|
200
|
+
method = @page.method.to_s.upcase if @page.method
|
|
201
|
+
|
|
202
|
+
if( res )
|
|
203
|
+
request_headers = res.request.headers
|
|
204
|
+
response_headers = res.headers
|
|
205
|
+
response = res.body
|
|
206
|
+
url = res.effective_url
|
|
207
|
+
method = res.request.method.to_s.upcase
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if response_headers['content-type'] &&
|
|
211
|
+
!response_headers['content-type'].substring?( 'text' )
|
|
212
|
+
response = nil
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
begin
|
|
216
|
+
print_ok( "In #{opts[:element]} var '#{opts[:altered]}' ( #{url} )" )
|
|
217
|
+
rescue
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
print_verbose( "Injected string:\t" + opts[:injected] ) if opts[:injected]
|
|
221
|
+
print_verbose( "Verified string:\t" + opts[:match].to_s ) if opts[:match]
|
|
222
|
+
print_verbose( "Matched regular expression: " + opts[:regexp].to_s )
|
|
223
|
+
print_debug( 'Request ID: ' + res.request.id.to_s ) if res
|
|
224
|
+
print_verbose( '---------' ) if only_positives?
|
|
225
|
+
|
|
226
|
+
# Instantiate a new Vulnerability class and
|
|
227
|
+
# append it to the results array
|
|
228
|
+
vuln = Issue.new( {
|
|
229
|
+
:var => opts[:altered],
|
|
230
|
+
:url => url,
|
|
231
|
+
:injected => opts[:injected],
|
|
232
|
+
:id => opts[:id],
|
|
233
|
+
:regexp => opts[:regexp],
|
|
234
|
+
:regexp_match => opts[:match],
|
|
235
|
+
:elem => opts[:element],
|
|
236
|
+
:verification => opts[:verification] || false,
|
|
237
|
+
:method => method,
|
|
238
|
+
:response => response,
|
|
239
|
+
:opts => opts,
|
|
240
|
+
:headers => {
|
|
241
|
+
:request => request_headers,
|
|
242
|
+
:response => response_headers,
|
|
243
|
+
}
|
|
244
|
+
}.merge( self.class.info ) )
|
|
245
|
+
register_results( [vuln] )
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
#
|
|
249
|
+
# Provides easy access to element auditing.
|
|
250
|
+
#
|
|
251
|
+
# If no elements have been specified in 'opts' it will
|
|
252
|
+
# use the elements from the module's "self.info()" hash. <br/>
|
|
253
|
+
# If no elements have been specified in 'opts' or "self.info()" it will
|
|
254
|
+
# use the elements in {OPTIONS}. <br/>
|
|
255
|
+
#
|
|
256
|
+
#
|
|
257
|
+
# @param [String] injection_str the string to be injected
|
|
258
|
+
# @param [Hash] opts options as described in {OPTIONS}
|
|
259
|
+
# @param [Block] &block block to be passed the:
|
|
260
|
+
# * HTTP response
|
|
261
|
+
# * name of the input vector
|
|
262
|
+
# * updated opts
|
|
263
|
+
# The block will be called as soon as the
|
|
264
|
+
# HTTP response is received.
|
|
265
|
+
#
|
|
266
|
+
def audit( injection_str, opts = { }, &block )
|
|
267
|
+
|
|
268
|
+
if( !opts.include?( :elements) || !opts[:elements] || opts[:elements].empty? )
|
|
269
|
+
opts[:elements] = self.class.info[:elements]
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
if( !opts.include?( :elements) || !opts[:elements] || opts[:elements].empty? )
|
|
273
|
+
opts[:elements] = OPTIONS[:elements]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
opts = OPTIONS.merge( opts )
|
|
277
|
+
|
|
278
|
+
opts[:elements].each {
|
|
279
|
+
|elem|
|
|
280
|
+
|
|
281
|
+
case elem
|
|
282
|
+
|
|
283
|
+
when Element::LINK
|
|
284
|
+
audit_links( injection_str, opts, &block )
|
|
285
|
+
|
|
286
|
+
when Element::FORM
|
|
287
|
+
audit_forms( injection_str, opts, &block )
|
|
288
|
+
|
|
289
|
+
when Element::COOKIE
|
|
290
|
+
audit_cookies( injection_str, opts, &block )
|
|
291
|
+
|
|
292
|
+
when Element::HEADER
|
|
293
|
+
audit_headers( injection_str, opts, &block )
|
|
294
|
+
else
|
|
295
|
+
raise( 'Unknown element to audit: ' + elem.to_s )
|
|
296
|
+
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
}
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
#
|
|
304
|
+
# Audits elements using a 2 phase timing attack and logs results.
|
|
305
|
+
#
|
|
306
|
+
# 'opts' needs to contain a :timeout value in milliseconds.</br>
|
|
307
|
+
# Optionally, you can add a :timeout_divider.
|
|
308
|
+
#
|
|
309
|
+
# Phase 1 uses the timeout value passed in opts, phase 2 uses (timeout * 2). </br>
|
|
310
|
+
# If phase 1 fails, phase 2 is aborted. </br>
|
|
311
|
+
# If we have a result in phase 1, phase 2 verifies that result with the higher timeout.
|
|
312
|
+
#
|
|
313
|
+
# @param [Array] strings injection strings
|
|
314
|
+
# '__TIME__' will be substituded with (timeout / timeout_divider)
|
|
315
|
+
# @param [Hash] opts options as described in {OPTIONS}
|
|
316
|
+
#
|
|
317
|
+
def audit_timeout( strings, opts )
|
|
318
|
+
logged = Set.new
|
|
319
|
+
|
|
320
|
+
delay = opts[:timeout]
|
|
321
|
+
|
|
322
|
+
audit_timeout_debug_msg( 1, delay )
|
|
323
|
+
timing_attack( strings, opts ) {
|
|
324
|
+
|res, opts, elem|
|
|
325
|
+
|
|
326
|
+
if !logged.include?( opts[:altered] )
|
|
327
|
+
logged << opts[:altered]
|
|
328
|
+
audit_timeout_phase_2( elem )
|
|
329
|
+
end
|
|
330
|
+
}
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
#
|
|
334
|
+
# Runs phase 2 of the timing attack auditng an individual element
|
|
335
|
+
# (which passed phase 1) with a higher delay and timeout
|
|
336
|
+
#
|
|
337
|
+
def audit_timeout_phase_2( elem )
|
|
338
|
+
|
|
339
|
+
opts = elem.opts
|
|
340
|
+
opts[:timeout] *= 2
|
|
341
|
+
|
|
342
|
+
audit_timeout_debug_msg( 2, opts[:timeout] )
|
|
343
|
+
|
|
344
|
+
str = opts[:timing_string].gsub( '__TIME__',
|
|
345
|
+
( (opts[:timeout] + 3000) / opts[:timeout_divider] ).to_s )
|
|
346
|
+
|
|
347
|
+
elem.auditor( self )
|
|
348
|
+
elem.audit( str, opts ) {
|
|
349
|
+
|res, opts|
|
|
350
|
+
|
|
351
|
+
if res.timed_out?
|
|
352
|
+
|
|
353
|
+
# all issues logged by timing attacks need manual verification.
|
|
354
|
+
# end of story.
|
|
355
|
+
opts[:verification] = true
|
|
356
|
+
log( opts, res)
|
|
357
|
+
end
|
|
358
|
+
}
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def audit_timeout_debug_msg( phase, delay )
|
|
362
|
+
print_debug( '---------------------------------------------' )
|
|
363
|
+
print_debug( "Running phase #{phase.to_s} of timing attack." )
|
|
364
|
+
print_debug( "Delay set to: #{delay.to_s} milliseconds" )
|
|
365
|
+
print_debug( '---------------------------------------------' )
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
#
|
|
369
|
+
# Audits elements using a timing attack.
|
|
370
|
+
#
|
|
371
|
+
# 'opts' needs to contain a :timeout value in milliseconds.</br>
|
|
372
|
+
# Optionally, you can add a :timeout_divider.
|
|
373
|
+
#
|
|
374
|
+
# @param [Array] strings injection strings
|
|
375
|
+
# '__TIME__' will be substituded with (timeout / timeout_divider)
|
|
376
|
+
# @param [Hash] opts options as described in {OPTIONS}
|
|
377
|
+
# @param [Block] &block block to call if a timeout occurs,
|
|
378
|
+
# it will be passed the response and opts
|
|
379
|
+
#
|
|
380
|
+
def timing_attack( strings, opts, &block )
|
|
381
|
+
|
|
382
|
+
opts[:timeout_divider] ||= 1
|
|
383
|
+
[strings].flatten.each {
|
|
384
|
+
|str|
|
|
385
|
+
|
|
386
|
+
opts[:timing_string] = str
|
|
387
|
+
str = str.gsub( '__TIME__', ( (opts[:timeout] + 3000) / opts[:timeout_divider] ).to_s )
|
|
388
|
+
audit( str, opts ) {
|
|
389
|
+
|res, opts, elem|
|
|
390
|
+
block.call( res, opts, elem ) if block && res.timed_out?
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
#
|
|
396
|
+
# Provides the following methods:
|
|
397
|
+
# * audit_links()
|
|
398
|
+
# * audit_forms()
|
|
399
|
+
# * audit_cookies()
|
|
400
|
+
# * audit_headers()
|
|
401
|
+
#
|
|
402
|
+
# Metaprogrammed to avoid redundant code while maintaining compatibility
|
|
403
|
+
# and method shortcuts.
|
|
404
|
+
#
|
|
405
|
+
# @see #audit_elems
|
|
406
|
+
#
|
|
407
|
+
def method_missing( sym, *args, &block )
|
|
408
|
+
|
|
409
|
+
elem = sym.to_s.gsub!( 'audit_', '@' )
|
|
410
|
+
raise NoMethodError.new( "Undefined method '#{sym.to_s}'.", sym, args ) if !elem
|
|
411
|
+
|
|
412
|
+
elems = @page.instance_variable_get( elem )
|
|
413
|
+
|
|
414
|
+
if( elems && elem )
|
|
415
|
+
raise ArgumentError.new( "Missing required argument 'injection_str'" +
|
|
416
|
+
" for audit_#{elem.gsub( '@', '' )}()." ) if( !args[0] )
|
|
417
|
+
audit_elems( elems, args[0], args[1] ? args[1]: {}, &block )
|
|
418
|
+
else
|
|
419
|
+
raise NoMethodError.new( "Undefined method '#{sym.to_s}'.", sym, args )
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
#
|
|
424
|
+
# Audits Auditalble HTML/HTTP elements
|
|
425
|
+
#
|
|
426
|
+
# @param [Array<Arachni::Element::Auditable>] elements auditable elements to audit
|
|
427
|
+
# @param [String] injection_str the string to be injected
|
|
428
|
+
# @param [Hash] opts options as described in {OPTIONS}
|
|
429
|
+
# @param [Block] &block block to be passed the:
|
|
430
|
+
# * HTTP response
|
|
431
|
+
# * name of the input vector
|
|
432
|
+
# * updated opts
|
|
433
|
+
# The block will be called as soon as the
|
|
434
|
+
# HTTP response is received.
|
|
435
|
+
#
|
|
436
|
+
# @see #method_missing
|
|
437
|
+
#
|
|
438
|
+
def audit_elems( elements, injection_str, opts = { }, &block )
|
|
439
|
+
|
|
440
|
+
opts = OPTIONS.merge( opts )
|
|
441
|
+
url = @page.url
|
|
442
|
+
|
|
443
|
+
opts[:injected_orig] = injection_str
|
|
444
|
+
|
|
445
|
+
elements.each{
|
|
446
|
+
|elem|
|
|
447
|
+
elem.auditor( self )
|
|
448
|
+
elem.audit( injection_str, opts, &block )
|
|
449
|
+
}
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
end
|
|
455
|
+
end
|