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.
Files changed (56) hide show
  1. data/.document +5 -0
  2. data/AUTHORS +5 -0
  3. data/ChangeLog +69 -0
  4. data/ISSUES +0 -0
  5. data/LICENSE +24 -0
  6. data/README +51 -0
  7. data/Rakefile +63 -0
  8. data/VERSION +1 -0
  9. data/lib/codec/base_codec.rb +99 -0
  10. data/lib/codec/css_codec.rb +101 -0
  11. data/lib/codec/encoder.rb +330 -0
  12. data/lib/codec/html_codec.rb +424 -0
  13. data/lib/codec/javascript_codec.rb +119 -0
  14. data/lib/codec/mysql_codec.rb +131 -0
  15. data/lib/codec/oracle_codec.rb +46 -0
  16. data/lib/codec/os_codec.rb +78 -0
  17. data/lib/codec/percent_codec.rb +53 -0
  18. data/lib/codec/pushable_string.rb +114 -0
  19. data/lib/codec/vbscript_codec.rb +64 -0
  20. data/lib/codec/xml_codec.rb +173 -0
  21. data/lib/esapi.rb +68 -0
  22. data/lib/exceptions.rb +37 -0
  23. data/lib/executor.rb +20 -0
  24. data/lib/owasp-esapi-ruby.rb +13 -0
  25. data/lib/sanitizer/xss.rb +59 -0
  26. data/lib/validator/base_rule.rb +90 -0
  27. data/lib/validator/date_rule.rb +92 -0
  28. data/lib/validator/email.rb +29 -0
  29. data/lib/validator/float_rule.rb +76 -0
  30. data/lib/validator/generic_validator.rb +26 -0
  31. data/lib/validator/integer_rule.rb +61 -0
  32. data/lib/validator/string_rule.rb +146 -0
  33. data/lib/validator/validator_error_list.rb +48 -0
  34. data/lib/validator/zipcode.rb +27 -0
  35. data/spec/codec/css_codec_spec.rb +61 -0
  36. data/spec/codec/html_codec_spec.rb +87 -0
  37. data/spec/codec/javascript_codec_spec.rb +45 -0
  38. data/spec/codec/mysql_codec_spec.rb +44 -0
  39. data/spec/codec/oracle_codec_spec.rb +23 -0
  40. data/spec/codec/os_codec_spec.rb +51 -0
  41. data/spec/codec/percent_codec_spec.rb +34 -0
  42. data/spec/codec/vbcript_codec_spec.rb +23 -0
  43. data/spec/codec/xml_codec_spec.rb +83 -0
  44. data/spec/owasp_esapi_encoder_spec.rb +226 -0
  45. data/spec/owasp_esapi_executor_spec.rb +9 -0
  46. data/spec/owasp_esapi_ruby_email_validator_spec.rb +39 -0
  47. data/spec/owasp_esapi_ruby_xss_sanitizer_spec.rb +66 -0
  48. data/spec/owasp_esapi_ruby_zipcode_validator_spec.rb +42 -0
  49. data/spec/spec_helper.rb +10 -0
  50. data/spec/validator/base_rule_spec.rb +29 -0
  51. data/spec/validator/date_rule_spec.rb +40 -0
  52. data/spec/validator/float_rule_spec.rb +31 -0
  53. data/spec/validator/integer_rule_spec.rb +51 -0
  54. data/spec/validator/string_rule_spec.rb +103 -0
  55. data/spec/validator_skeleton.rb +150 -0
  56. metadata +235 -0
@@ -0,0 +1,173 @@
1
+ # Implementation of the Codec interface for XML entity encoding.
2
+ # This differes from HTML entity encoding in that only the following
3
+ # named entities are predefined:
4
+ # * lt
5
+ # * gt
6
+ # * amp
7
+ # * apos
8
+ # * quot
9
+ #
10
+ # However, the XML Specification 1.0 states in section 4.6 "Predefined
11
+ # Entities" that these should still be declared for interoperability
12
+ # purposes. As such, encoding in this class will not use them.
13
+ #
14
+ # It's also worth noting that unlike the HTMLEntityCodec, a trailing
15
+ # semicolon is required and all valid codepoints are accepted.
16
+ #
17
+ # Note that it is a REALLY bad idea to use this for decoding as an XML
18
+ # document can declare arbitrary entities that this Codec has no way
19
+ # of knowing about. Decoding is included for completeness but it's use
20
+ # is not recommended. Use a XML parser instead!
21
+
22
+ module Owasp
23
+ module Esapi
24
+ module Codec
25
+ class XmlCodec < BaseCodec
26
+
27
+ def initialize
28
+ @longest_key = 0
29
+ @lookup_map = {}
30
+ ENTITY_MAP.each_key do |k|
31
+ if k.size > @longest_key
32
+ @longest_key += 1
33
+ end
34
+ @lookup_map[k.downcase] = k
35
+ end
36
+ end
37
+
38
+ # Encodes a Character using XML entities as necessary.
39
+ def encode_char(immune,input)
40
+ return input if immune.include?(input)
41
+ return input if input =~ /[a-zA-Z0-9\\t ]/
42
+ return "&#x#{hex(input)};"
43
+ end
44
+
45
+ # Returns the decoded version of the character starting at index, or
46
+ # nil if no decoding is possible.
47
+ def decode_char(input)
48
+ input.mark
49
+ result = nil
50
+ # check first
51
+ first = input.next
52
+ return nil if first.nil?
53
+ return first unless first == "&"
54
+ # check second
55
+ second = input.next
56
+ if second == "#"
57
+ result = numeric_entity(input)
58
+ elsif second =~ /[a-zA-Z]/
59
+ input.push(second)
60
+ result = named_entity(input)
61
+ else
62
+ input.push(second)
63
+ return nil
64
+ end
65
+
66
+ if result.nil?
67
+ input.reset
68
+ end
69
+ result
70
+ end
71
+
72
+ def numeric_entity(input) #:nodoc:
73
+ first = input.peek
74
+ return nil if first.nil?
75
+ if first.downcase.eql?("x")
76
+ input.next
77
+ return parse_hex(input)
78
+ end
79
+ return parse_number(input)
80
+ end
81
+
82
+ # parse the hex value back to its decimal value
83
+ def parse_hex(input) #:nodoc:
84
+ result = ''
85
+ while input.next?
86
+ c = input.peek
87
+ if "0123456789ABCDEFabcdef".include?(c)
88
+ result << c
89
+ input.next
90
+ elsif c == ";"
91
+ input.next
92
+ break
93
+ else
94
+ return nil
95
+ end
96
+ end
97
+ begin
98
+ i = result.hex
99
+ return i.chr(Encoding::UTF_8) if i >= START_CODE_POINT and i <= END_CODE_POINT
100
+ rescue Exception => e
101
+ end
102
+ nil
103
+ end
104
+
105
+ # parse a number out of the encoded value
106
+ def parse_number(input) #:nodoc:
107
+ result = ''
108
+ missing_semi = true
109
+ while input.next?
110
+ c = input.peek
111
+ if c =~ /\d/
112
+ result << c
113
+ input.next
114
+ elsif c == ';'
115
+ input.next
116
+ break;
117
+ elsif not c =~ /\d/
118
+ return nil
119
+ else
120
+ break;
121
+ end
122
+ end
123
+
124
+ begin
125
+ i = result.to_i
126
+ return i.chr(Encoding::UTF_8) if i >= START_CODE_POINT and i <= END_CODE_POINT
127
+ rescue Exception => e
128
+ end
129
+ nil
130
+ end
131
+
132
+ # extract the named entity fromt he input
133
+ # we convert the entity to the real character i.e. &amp; becoems &
134
+ def named_entity(input) #:nodoc:
135
+ possible = ''
136
+ len = min(input.remainder.size,@longest_key+1)
137
+ found_key = false
138
+ last_possible = ''
139
+ for i in 0..len do
140
+ possible << input.next if input.next?
141
+ # we have to find the longest match
142
+ # so we dont find sub values
143
+ if @lookup_map[possible.downcase]
144
+ last_possible = @lookup_map[possible.downcase]
145
+ end
146
+ end
147
+ # no matches found return
148
+ return nil if last_possible.empty?
149
+ return nil unless possible.include?(";")
150
+ # reset the input and plow through
151
+ input.reset
152
+ for i in 0..last_possible.size
153
+ input.next if input.next?
154
+ end
155
+ possible = ENTITY_MAP[last_possible]
156
+ input.next # consume the ;
157
+ return possible unless possible.empty?
158
+ return nil
159
+ end
160
+
161
+ # Entity maps
162
+ ENTITY_MAP = {
163
+ "lt" => "<",
164
+ "gt" => ">",
165
+ "amp" => "&",
166
+ "apos" => "\'",
167
+ "quot" => "\""
168
+ }
169
+
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,68 @@
1
+ #
2
+ # Class loading mechanism, we use this to create new instances of objects based
3
+ # on config data. This allows a user to set their own config for instance to use thier
4
+ # own implmentation of a given class. ClassLoader based on Rails constantize
5
+ #
6
+ class ClassLoader
7
+ def self.load_class(class_name)
8
+ # we are using ruby 1.9.2 as a requirement, so we can use the inheritance
9
+ # of const_get to find our object. if mis-spelled it will raise a NameError
10
+ names = class_name.split("::")
11
+ klass = Object
12
+ names.each do |name|
13
+ klass = klass.const_get(name)
14
+ end
15
+ klass.new
16
+ end
17
+ end
18
+
19
+ # Owasp root modules
20
+ module Owasp
21
+ # Configuration class
22
+ class Configuration
23
+ attr_accessor :logger, :encoder
24
+ # Is intrustion detectione nabled?
25
+ def ids?
26
+ return true
27
+ end
28
+ # Get the encoder class anem
29
+ def get_encoder_class
30
+
31
+ end
32
+ end
33
+ # Logging class stub
34
+ class Logger
35
+ def warn(msg)
36
+ #puts "WARNING: #{msg}"
37
+ end
38
+ end
39
+ # Esapi Root module
40
+ module Esapi
41
+
42
+ # seutp ESAPI
43
+ def self.setup
44
+ @config ||= Configuration.new
45
+ yield @config if block_given?
46
+ process_config(@config)
47
+ end
48
+
49
+ # Get the security configuration context
50
+ def self.security_config
51
+ @security ||= Configuration.new
52
+ end
53
+ # Get the configured logger
54
+ def self.logger
55
+ @logger ||= Logger.new
56
+ end
57
+ # Get the configured encoded
58
+ def self.encoder
59
+ @encoder ||= ClassLoader.load_class("Owasp::Esapi::Encoder")
60
+ end
61
+
62
+ private
63
+ # Process the config data to setup esapi
64
+ def self.process_config(conf)
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,37 @@
1
+ # Various exception used by Esapi
2
+ module Owasp
3
+ module Esapi
4
+
5
+ # Base Exception class for SecurityExceptions
6
+ class EnterpriseSecurityException < Exception
7
+ attr :log_message
8
+ def initialize(user_msg, log_msg)
9
+ super(user_msg)
10
+ @log_message = log_msg
11
+ end
12
+ end
13
+
14
+ # Exception throw if there is an error during Executor processing
15
+ class ExecutorException < EnterpriseSecurityException
16
+ end
17
+
18
+ # Intrustion detection exception to be logged
19
+ class IntrustionException < Exception
20
+ attr :log_message
21
+ def initialize(user_message,log_message)
22
+ super(user_message)
23
+ @log_message = log_message
24
+ end
25
+ end
26
+
27
+ # ValidatorException used in the rule sets
28
+ class ValidationException < EnterpriseSecurityException
29
+ attr :context
30
+ def initialize(user_msg,log_msg,context)
31
+ super(user_msg,log_msg)
32
+ @context = context
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ # Executor implmentation
2
+ #
3
+ # Provide a safe execute command, that wll ensure paths and args are escaped properly
4
+ # and check for expansions of the command
5
+ #
6
+
7
+ module Owasp
8
+ module Esapi
9
+ # Executor class
10
+ class Executor
11
+
12
+ # Wrapper for Process#spawn
13
+ # it sanitizes the parames and validates paths before execution
14
+ def execute_command(cmd,params,working_dir,codec,redirect_error)
15
+ cmd_path = File.expand_path(cmd)
16
+
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ require 'esapi'
2
+ require 'exceptions'
3
+ require 'sanitizer/xss'
4
+ require 'validator/zipcode'
5
+ require 'validator/email'
6
+ require 'codec/encoder'
7
+ require 'validator/validator_error_list'
8
+ require 'validator/base_rule'
9
+ require 'validator/string_rule'
10
+ require 'validator/date_rule'
11
+ require 'validator/integer_rule'
12
+ require 'validator/float_rule'
13
+ require 'executor'
@@ -0,0 +1,59 @@
1
+ module Owasp
2
+ module Esapi
3
+ module Sanitizer
4
+
5
+ # This is the Cross site scripting sanitizer class.
6
+ # {http://bit.ly/AJVmn The XSS Cheat sheet at Owasp site}
7
+ class Xss
8
+
9
+ attr_accessor :smart
10
+ # Creates a new sanitizer
11
+ # @param [Boolean], smart.
12
+ # A boolean that says if sanitizer can blindly escape all 'dangerous' characters
13
+ # in their html entity or rather if it should try to guess if the string needs
14
+ # sanitizing is a xss attack vector or not and then let the string to pass by.
15
+ def initialize(smart=false)
16
+ self.smart= smart
17
+ end
18
+
19
+ # Todo, we should really investigate if dangerous chars have to be trimmed or substituted.
20
+ # I'm (Paolo) choosing substitute right now... we'll change it later.
21
+ # @param [String], tainted. The string needs to be sanitized
22
+ # @return [String] the input string sanitized equivalent
23
+ def sanitize(tainted)
24
+ untainted = tainted
25
+
26
+ untainted = rule1_sanitize(tainted)
27
+
28
+ # Start - RULE #2 - Attribute Escape Before Inserting Untrusted Data into HTML Common Attributes
29
+ # End - RULE #2 - Attribute Escape Before Inserting Untrusted Data into HTML Common Attributes
30
+
31
+ # Start - RULE #3 - JavaScript Escape Before Inserting Untrusted Data into HTML JavaScript Data Values
32
+ # End - RULE #3 - JavaScript Escape Before Inserting Untrusted Data into HTML JavaScript Data Values
33
+
34
+ # Start - RULE #4 - CSS Escape Before Inserting Untrusted Data into HTML Style Property Values
35
+ # End - RULE #4 - CSS Escape Before Inserting Untrusted Data into HTML Style Property Values
36
+
37
+ untainted
38
+ end
39
+ private
40
+ def rule1_sanitize(taint)
41
+ untainted = taint
42
+ # Start - RULE #1 - HTML Escape Before Inserting Untrusted Data into HTML Element Content
43
+
44
+ # This *must* be the first substitution, otherwise it will substitute also & characters in
45
+ # valid HTML entities
46
+ untainted = untainted.gsub("&", "&amp;")
47
+ untainted = untainted.gsub("<", "&lt;")
48
+ untainted = untainted.gsub(">", "&gt;")
49
+ untainted = untainted.gsub("\"", "&quot;")
50
+ untainted = untainted.gsub("\'", "&#x27;")
51
+ untainted = untainted.gsub("/", "&#x2F;")
52
+
53
+ # End - RULE #1 - HTML Escape Before Inserting Untrusted Data into HTML Element Content
54
+ untainted
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,90 @@
1
+ # Expand Integer to add Min and Max values
2
+ class Integer #:nodoc:
3
+ N_BYTES = [42].pack('i').size
4
+ N_BITS = N_BYTES * 8
5
+ MAX = 2 ** (N_BITS - 2) - 1
6
+ MIN = -MAX - 1
7
+ end
8
+
9
+ module Owasp
10
+ module Esapi
11
+ module Validator
12
+
13
+ # A ValidationRule performs syntax and possibly semantic validation of a single
14
+ # piece of data from an untrusted source.
15
+ class BaseRule
16
+ attr_accessor :encoder, :name, :allow_nil
17
+ def initialize(name,encoder=nil)
18
+ @name = name
19
+ @encoder = encoder
20
+ @encoder = Owasp::Esapi.encoder if @encoder.nil?
21
+ @allow_nil = false
22
+ end
23
+
24
+ # return true if the input passes validation
25
+ def valid?(context,input)
26
+ valid = false
27
+ begin
28
+ valid(context,input)
29
+ valid = true
30
+ rescue Exception =>e
31
+ end
32
+ valid
33
+ end
34
+
35
+ # Parse the input, calling the valid method
36
+ # if an exception if thrown it will be added
37
+ # to the ValidatorErrorList object. This method allows for multiple rules to be executed
38
+ # and collect all the errors that were invoked along the way.
39
+ def validate(context,input, errors=nil)
40
+ valid = nil
41
+ begin
42
+ valid = valid(context,input)
43
+ rescue ValidationException => e
44
+ errors<< e unless errors.nil?
45
+ end
46
+ input
47
+ end
48
+
49
+ # Parse the input, raise exceptions if validation fails
50
+ # sub classes need to implment this method as the base class will always raise an
51
+ # exception
52
+ def valid(context,input)
53
+ raise Owasp::Esapi::ValidationException.new(input,input,context)
54
+ end
55
+
56
+ # Try to call get *valid*, then call sanitize, finally return a default value
57
+ def safe(context,string)
58
+ valid = nil
59
+ begin
60
+ valid = valid(context,input)
61
+ rescue ValidationException => e
62
+ return sanitize(context,input)
63
+ end
64
+ return valid
65
+ end
66
+
67
+ # The method is similar to getSafe except that it returns a
68
+ # harmless object that <b>may or may not have any similarity to the original
69
+ # input (in some cases you may not care)</b>. In most cases this should be the
70
+ # same as the getSafe method only instead of throwing an exception, return
71
+ # some default value. Subclasses should implment this method
72
+ def sanitize(context,input)
73
+ input
74
+ end
75
+
76
+ # Removes characters that aren't in the whitelist from the input String.
77
+ # chars is expected to be string
78
+ def whitelist(input,list)
79
+ rc = ''
80
+ input.chars do |c|
81
+ rc << c if list.include?(c)
82
+ end
83
+ rc
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
90
+ end