owasp-esapi-ruby 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
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