owasp-esapi-ruby 0.30.0
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/.document +5 -0
- data/AUTHORS +5 -0
- data/ChangeLog +69 -0
- data/ISSUES +0 -0
- data/LICENSE +24 -0
- data/README +51 -0
- data/Rakefile +63 -0
- data/VERSION +1 -0
- data/lib/codec/base_codec.rb +99 -0
- data/lib/codec/css_codec.rb +101 -0
- data/lib/codec/encoder.rb +330 -0
- data/lib/codec/html_codec.rb +424 -0
- data/lib/codec/javascript_codec.rb +119 -0
- data/lib/codec/mysql_codec.rb +131 -0
- data/lib/codec/oracle_codec.rb +46 -0
- data/lib/codec/os_codec.rb +78 -0
- data/lib/codec/percent_codec.rb +53 -0
- data/lib/codec/pushable_string.rb +114 -0
- data/lib/codec/vbscript_codec.rb +64 -0
- data/lib/codec/xml_codec.rb +173 -0
- data/lib/esapi.rb +68 -0
- data/lib/exceptions.rb +37 -0
- data/lib/executor.rb +20 -0
- data/lib/owasp-esapi-ruby.rb +13 -0
- data/lib/sanitizer/xss.rb +59 -0
- data/lib/validator/base_rule.rb +90 -0
- data/lib/validator/date_rule.rb +92 -0
- data/lib/validator/email.rb +29 -0
- data/lib/validator/float_rule.rb +76 -0
- data/lib/validator/generic_validator.rb +26 -0
- data/lib/validator/integer_rule.rb +61 -0
- data/lib/validator/string_rule.rb +146 -0
- data/lib/validator/validator_error_list.rb +48 -0
- data/lib/validator/zipcode.rb +27 -0
- data/spec/codec/css_codec_spec.rb +61 -0
- data/spec/codec/html_codec_spec.rb +87 -0
- data/spec/codec/javascript_codec_spec.rb +45 -0
- data/spec/codec/mysql_codec_spec.rb +44 -0
- data/spec/codec/oracle_codec_spec.rb +23 -0
- data/spec/codec/os_codec_spec.rb +51 -0
- data/spec/codec/percent_codec_spec.rb +34 -0
- data/spec/codec/vbcript_codec_spec.rb +23 -0
- data/spec/codec/xml_codec_spec.rb +83 -0
- data/spec/owasp_esapi_encoder_spec.rb +226 -0
- data/spec/owasp_esapi_executor_spec.rb +9 -0
- data/spec/owasp_esapi_ruby_email_validator_spec.rb +39 -0
- data/spec/owasp_esapi_ruby_xss_sanitizer_spec.rb +66 -0
- data/spec/owasp_esapi_ruby_zipcode_validator_spec.rb +42 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/validator/base_rule_spec.rb +29 -0
- data/spec/validator/date_rule_spec.rb +40 -0
- data/spec/validator/float_rule_spec.rb +31 -0
- data/spec/validator/integer_rule_spec.rb +51 -0
- data/spec/validator/string_rule_spec.rb +103 -0
- data/spec/validator_skeleton.rb +150 -0
- metadata +235 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
#
|
2
|
+
# Codec to provide for MySQL string support
|
3
|
+
# http://mirror.yandex.ru/mirrors/ftp.mysql.com/doc/refman/5.0/en/string-syntax.html for details
|
4
|
+
module Owasp
|
5
|
+
module Esapi
|
6
|
+
module Codec
|
7
|
+
class MySQLCodec < BaseCodec
|
8
|
+
MYSQL_MODE = 0 # MySQL standard mode
|
9
|
+
ANSI_MODE = 1; # ANSI escape mode
|
10
|
+
|
11
|
+
# create a mysql codec.
|
12
|
+
# mode must be either MYSQL_MODE or ANSI_MODE
|
13
|
+
# The mode sets wether to use ansi mode in mysql or not
|
14
|
+
# defaults to MYSQL_MODE
|
15
|
+
def initialize(mode = 0)
|
16
|
+
if mode < MYSQL_MODE or mode > ANSI_MODE
|
17
|
+
raise RangeError.new()
|
18
|
+
end
|
19
|
+
@mode = mode
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns quote-encoded *character*
|
23
|
+
def encode_char(immune,input)
|
24
|
+
return input if immune.include?(input)
|
25
|
+
hex = hex(input)
|
26
|
+
return input if hex.nil?
|
27
|
+
return to_ansi(input) if @mode == ANSI_MODE
|
28
|
+
return to_mysql(input) if @mode == MYSQL_MODE
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the decoded version of the character starting at index, or
|
32
|
+
# nil if no decoding is possible.
|
33
|
+
#
|
34
|
+
# Formats all are legal (case sensitive)
|
35
|
+
# In ANSI_MODE '' decodes to '
|
36
|
+
# In MYSQL_MODE \x decodes to x (or a small list of specials)
|
37
|
+
def decode_char(input)
|
38
|
+
return from_ansi(input) if @mode == ANSI_MODE
|
39
|
+
return from_mysql(input) if @mode == MYSQL_MODE
|
40
|
+
end
|
41
|
+
|
42
|
+
# encode ' only
|
43
|
+
def to_ansi(input) #:nodoc:
|
44
|
+
return "\'\'" if input == "\'"
|
45
|
+
input
|
46
|
+
end
|
47
|
+
|
48
|
+
# encode for NO_BACKLASH_MODE
|
49
|
+
def to_mysql(input) # :nodoc:
|
50
|
+
c = input.ord
|
51
|
+
return "\\0" if c == 0x00
|
52
|
+
return "\\b" if c == 0x08
|
53
|
+
return "\\t" if c == 0x09
|
54
|
+
return "\\n" if c == 0x0a
|
55
|
+
return "\\r" if c == 0x0d
|
56
|
+
return "\\Z" if c == 0x1a
|
57
|
+
return "\\\"" if c == 0x22
|
58
|
+
return "\\%" if c == 0x25
|
59
|
+
return "\\'" if c == 0x27
|
60
|
+
return "\\\\" if c == 0x5c
|
61
|
+
return "\\_" if c == 0x5f
|
62
|
+
"\\#{input}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# decode a char with ansi only compliane i.e. apostrohpe only
|
66
|
+
def from_ansi(input) # :nodoc:
|
67
|
+
input.mark
|
68
|
+
first = input.next
|
69
|
+
|
70
|
+
# check first char
|
71
|
+
if first.nil?
|
72
|
+
input.reset
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
|
76
|
+
unless first == "\'"
|
77
|
+
input.reset
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
# check second char
|
82
|
+
second = input.next
|
83
|
+
if second.nil?
|
84
|
+
input.reset
|
85
|
+
return nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# if second isnt an encoded char return nil
|
89
|
+
unless second == "\'"
|
90
|
+
input.reset
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
"\'"
|
94
|
+
end
|
95
|
+
|
96
|
+
# decode a char using mysql NO_BACKSLAH_QUOTE rules
|
97
|
+
def from_mysql(input) # :nodoc:
|
98
|
+
input.mark
|
99
|
+
# check first
|
100
|
+
first = input.next
|
101
|
+
if first.nil?
|
102
|
+
input.reset
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
|
106
|
+
# check second
|
107
|
+
second = input.next
|
108
|
+
if second.nil?
|
109
|
+
input.reset
|
110
|
+
return nil
|
111
|
+
end
|
112
|
+
|
113
|
+
return 0x00.chr if second == "0"
|
114
|
+
return 0x08.chr if second == "b"
|
115
|
+
return 0x08.chr if second == "t"
|
116
|
+
return 0x0a.chr if second == "n"
|
117
|
+
return 0x0d.chr if second == "r"
|
118
|
+
return 0x1a.chr if second == "z"
|
119
|
+
return 0x22.chr if second == "\""
|
120
|
+
return 0x25.chr if second == "%"
|
121
|
+
return 0x27.chr if second == "\'"
|
122
|
+
return 0x5c.chr if second == "\\"
|
123
|
+
return 0x5f.chr if second == "_"
|
124
|
+
# not an escape
|
125
|
+
second
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# Codec to provide for Oracle string support
|
3
|
+
# see http://oraqa.com/2006/03/20/how-to-escape-single-quotes-in-strings for details
|
4
|
+
# This will only prevent SQLinjection in the case of user data being placed within an
|
5
|
+
# Oracle quoted string such as select * from table where field = ' USERDATA '
|
6
|
+
module Owasp
|
7
|
+
module Esapi
|
8
|
+
module Codec
|
9
|
+
class OracleCodec < BaseCodec
|
10
|
+
|
11
|
+
# Encodes ' to ''
|
12
|
+
def encode_char(immune,input)
|
13
|
+
return "\'\'" if input == "\'"
|
14
|
+
input
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the decoded version of the character starting at index, or
|
18
|
+
# nil if no decoding is possible.
|
19
|
+
#
|
20
|
+
# Formats all are legal
|
21
|
+
# '' decodes to '
|
22
|
+
def decode_char(input)
|
23
|
+
# check first *char*
|
24
|
+
input.mark
|
25
|
+
first = input.next
|
26
|
+
if first.nil?
|
27
|
+
input.reset
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
# if it isnt an encoded string return nil
|
31
|
+
unless first == "\'"
|
32
|
+
input.reset
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
# if second isnt an encoded marker return nil
|
36
|
+
second = input.next
|
37
|
+
unless second == "\'"
|
38
|
+
input.reset
|
39
|
+
return nil
|
40
|
+
end
|
41
|
+
return "\'"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
# Operating system codec for escape characters for HOST commands
|
4
|
+
# We look at Unix style (max, linux) and Windows style
|
5
|
+
module Owasp
|
6
|
+
module Esapi
|
7
|
+
module Codec
|
8
|
+
class OsCodec < BaseCodec
|
9
|
+
# Window Host flag
|
10
|
+
WINDOWS_HOST = :Windows
|
11
|
+
# Unix Host flag
|
12
|
+
UNIX_HOST = :Unix
|
13
|
+
|
14
|
+
# Setup the code, if no os is passed in the codec
|
15
|
+
# will guess the OS based on the ruby host_os variable
|
16
|
+
def initialize(os = nil)
|
17
|
+
@host = nil
|
18
|
+
@escape_char = ''
|
19
|
+
host_os = os
|
20
|
+
if os.nil?
|
21
|
+
host_os = case Config::CONFIG['host_os']
|
22
|
+
when /mswin|windows/i then WINDOWS_HOST
|
23
|
+
when /linux/i then UNIX_HOST
|
24
|
+
when /darwin/i then UNIX_HOST
|
25
|
+
when /sunos|solaris/i then UNIX_HOST
|
26
|
+
else UNIX_HOST
|
27
|
+
end
|
28
|
+
end
|
29
|
+
if host_os == WINDOWS_HOST
|
30
|
+
@host = WINDOWS_HOST
|
31
|
+
@escape_char = '^'
|
32
|
+
elsif host_os == UNIX_HOST
|
33
|
+
@host = UNIX_HOST
|
34
|
+
@escape_char = '\\'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# get the configured OS
|
39
|
+
def os
|
40
|
+
@host
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns shell encoded character
|
44
|
+
# ^ - for windows
|
45
|
+
# \\ - for unix
|
46
|
+
def encode_char(immune,input)
|
47
|
+
return input if immune.include?(input)
|
48
|
+
return input if hex(input).nil?
|
49
|
+
return "#{@escape_char}#{input}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the decoded version of the character starting at index, or
|
53
|
+
# nil if no decoding is possible.
|
54
|
+
# <p>
|
55
|
+
# Formats all are legal both upper/lower case:
|
56
|
+
# ^x - all special characters when configured for WINDOWS
|
57
|
+
# \\ - all special characters when configured for UNIX
|
58
|
+
def decode_char(input)
|
59
|
+
input.mark
|
60
|
+
first = input.next
|
61
|
+
# check first char
|
62
|
+
if first.nil?
|
63
|
+
input.reset
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
# if it isnt escape return nil
|
67
|
+
if first != @escape_char
|
68
|
+
input.reset
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
# get teh escape value
|
72
|
+
return input.next
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Implementation of the Codec interface for percent encoding (aka URL encoding).
|
2
|
+
module Owasp
|
3
|
+
module Esapi
|
4
|
+
module Codec
|
5
|
+
class PercentCodec < BaseCodec
|
6
|
+
|
7
|
+
# Encode a character for URLs
|
8
|
+
def encode_char(immune,input)
|
9
|
+
return input if input =~ /[a-zA-Z0-9_.-]/
|
10
|
+
# RFC compliance
|
11
|
+
return "+" if input == " "
|
12
|
+
val = ''
|
13
|
+
input.each_byte do |b|
|
14
|
+
val << '%' << b.ord.to_h.upcase
|
15
|
+
end
|
16
|
+
val
|
17
|
+
end
|
18
|
+
|
19
|
+
# Formats all are legal both upper/lower case:
|
20
|
+
# %hh;
|
21
|
+
def decode_char(input)
|
22
|
+
input.mark
|
23
|
+
first = input.next
|
24
|
+
if first.nil?
|
25
|
+
input.reset
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
# check if this is an encoded character
|
29
|
+
if first != '%'
|
30
|
+
input.reset
|
31
|
+
return nil
|
32
|
+
end
|
33
|
+
# search for 2 hex digits
|
34
|
+
tmp = ''
|
35
|
+
for i in 0..1 do
|
36
|
+
c = input.next_hex
|
37
|
+
tmp << c unless c.nil?
|
38
|
+
end
|
39
|
+
# we found 2, convert to a number
|
40
|
+
if tmp.size == 2
|
41
|
+
i = tmp.hex
|
42
|
+
begin
|
43
|
+
return i.chr(Encoding::UTF_8) if i >= START_CODE_POINT and i <= END_CODE_POINT
|
44
|
+
rescue Exception => e
|
45
|
+
end
|
46
|
+
end
|
47
|
+
input.reset
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# The pushback string is used by Codecs to allow them to push decoded characters back onto a string
|
2
|
+
# for further decoding. This is necessary to detect double-encoding.
|
3
|
+
|
4
|
+
module Owasp
|
5
|
+
module Esapi
|
6
|
+
module Codec
|
7
|
+
class PushableString
|
8
|
+
attr :index
|
9
|
+
|
10
|
+
#
|
11
|
+
# Setup a pushable string
|
12
|
+
# stream will setup UTF_8 encoding on the input
|
13
|
+
def initialize(string)
|
14
|
+
@input = string.force_encoding(Encoding::UTF_8)
|
15
|
+
@index = 0
|
16
|
+
@mark = 0
|
17
|
+
@temp = nil
|
18
|
+
@push = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get the next token off of the stream
|
22
|
+
def next
|
23
|
+
unless @push.nil?
|
24
|
+
t = @push
|
25
|
+
@push = nil
|
26
|
+
return t
|
27
|
+
end
|
28
|
+
return nil if @input.nil?
|
29
|
+
return nil if @input.size == 0
|
30
|
+
return nil if @index >= @input.size
|
31
|
+
|
32
|
+
t = @input[@index]
|
33
|
+
@index += 1
|
34
|
+
t
|
35
|
+
end
|
36
|
+
|
37
|
+
# fetch the next hex token in the string or nil
|
38
|
+
def next_hex
|
39
|
+
c = self.next
|
40
|
+
return nil if c.nil?
|
41
|
+
return c if hex?(c)
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fetch the next octal token int eh string or nil
|
46
|
+
def next_octal
|
47
|
+
c = self.next
|
48
|
+
return nil if c.nil?
|
49
|
+
return c if octal?(c)
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
# Check to see if we have another token on the stream
|
54
|
+
def next?
|
55
|
+
!@push.nil? ? true : @input.nil? ? false : @input.empty? ? false : @index >= @input.length ? false : true
|
56
|
+
end
|
57
|
+
|
58
|
+
# Push a character back onto the string, this is a unread operation
|
59
|
+
def push(c)
|
60
|
+
@push = c
|
61
|
+
end
|
62
|
+
|
63
|
+
# Peek into teh stream and see if the next character is the one in question
|
64
|
+
def peek?(c)
|
65
|
+
return true if !@push.nil? and @push == c
|
66
|
+
return false if @input.empty?
|
67
|
+
return false if @input.nil?
|
68
|
+
return false if @index >= @input.size
|
69
|
+
@input[@index] == c
|
70
|
+
end
|
71
|
+
|
72
|
+
# Peek into the stream and fetch teh next character without moving the index
|
73
|
+
def peek
|
74
|
+
return @push if !@push.nil?
|
75
|
+
return nil if @input.nil?
|
76
|
+
return nil if @input.empty?
|
77
|
+
return nil if @index >= @input.size
|
78
|
+
@input[@index]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Mark the stream for rewind
|
82
|
+
def mark
|
83
|
+
@temp = @push
|
84
|
+
@mark = @index
|
85
|
+
end
|
86
|
+
|
87
|
+
# Check if a given character is a hexadecimal character
|
88
|
+
def hex?(c)
|
89
|
+
return false if c.nil?
|
90
|
+
c =~ /[a-fA-F0-9]/
|
91
|
+
end
|
92
|
+
|
93
|
+
# Check if a given character is an octal character
|
94
|
+
def octal?(c)
|
95
|
+
return false if c.nil?
|
96
|
+
c =~ /[0-7]/
|
97
|
+
end
|
98
|
+
|
99
|
+
# Reset the index back to the mark
|
100
|
+
def reset
|
101
|
+
@push = @temp
|
102
|
+
@index = @mark
|
103
|
+
end
|
104
|
+
|
105
|
+
# Fetch the rest of the string from the current index
|
106
|
+
def remainder
|
107
|
+
t = @input.slice(@index,@input.size-@index)
|
108
|
+
return @push + t unless @push.nil?
|
109
|
+
t
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Implementation of the Codec interface for 'quote' encoding from VBScript.
|
2
|
+
module Owasp
|
3
|
+
module Esapi
|
4
|
+
module Codec
|
5
|
+
class VbScriptCodec < BaseCodec
|
6
|
+
|
7
|
+
# Encode a String so that it can be safely used in a specific context.
|
8
|
+
def encode(immune, input)
|
9
|
+
encoded_string = ''
|
10
|
+
encoding = false
|
11
|
+
inquotes = false
|
12
|
+
encoded_string.encode!(Encoding::UTF_8)
|
13
|
+
i = 0
|
14
|
+
input.encode(Encoding::UTF_8).chars do |c|
|
15
|
+
if Owasp::Esapi::Encoder::CHAR_ALPHANUMERIC.include?(c) or immune.include?(c)
|
16
|
+
encoded_string << "&" if encoding and i > 0
|
17
|
+
encoded_string << "\"" if !inquotes and i > 0
|
18
|
+
encoded_string << c
|
19
|
+
inquotes = true
|
20
|
+
encoding = false
|
21
|
+
else
|
22
|
+
encoded_string << "\"" if inquotes and i < input.size
|
23
|
+
encoded_string << "&" if i > 0
|
24
|
+
encoded_string << encode_char(immune,c)
|
25
|
+
inquotes = false
|
26
|
+
encoding = true
|
27
|
+
end
|
28
|
+
i += 1
|
29
|
+
end
|
30
|
+
encoded_string
|
31
|
+
end
|
32
|
+
# Returns quote-encoded character
|
33
|
+
def encode_char(immune,input)
|
34
|
+
return input if immune.include?(input)
|
35
|
+
hex = hex(input)
|
36
|
+
return input if hex.nil?
|
37
|
+
return "chrw(#{input.ord})"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the decoded version of the character starting at index, or
|
41
|
+
# nil if no decoding is possible.
|
42
|
+
#
|
43
|
+
# Formats all are legal both upper/lower case:
|
44
|
+
# "x - all special characters
|
45
|
+
# " + chr(x) + " - not supported
|
46
|
+
|
47
|
+
def decode_char(input)
|
48
|
+
input.mark();
|
49
|
+
first = input.next
|
50
|
+
if first.nil?
|
51
|
+
input.reset
|
52
|
+
return nil;
|
53
|
+
end
|
54
|
+
# if this is not an encoded character, return null
|
55
|
+
if first != "\""
|
56
|
+
input.reset
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
input.next
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|