Class: Owasp::Esapi::Encoder

Inherits:
Object
  • Object
show all
Defined in:
lib/codec/encoder.rb

Constant Summary collapse

IMMUNE_CSS =

Immune Character feilds

[ ]
IMMUNE_HTMLATTR =
[ ',', '.', '-', '_' ]
IMMUNE_HTML =
[ ',', '.', '-', '_', ' ' ]
IMMUNE_JAVASCRIPT =
[ ',', '.', '_' ]
IMMUNE_VBSCRIPT =
[ ',', '.', '_' ]
IMMUNE_XML =
[ ',', '.', '-', '_', ' ' ]
IMMUNE_SQL =
[ ' ' ]
IMMUNE_OS =
[ '-' ]
IMMUNE_XMLATTR =
[ ',', '.', '-', '_' ]
IMMUNE_XPATH =
[ ',', '.', '-', '_', ' ' ]
PASSWORD_SPECIALS =
"!$*-.=?@_"
CHAR_LCASE =

Standard Characetr Sets

"abcdefghijklmnopqrstuvwxyz"
CHAR_UCASE =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
CHAR_DIGITS =
"0123456789"
CHAR_SPECIALS =
"!$*+-.=?@^_|~"
CHAR_LETTERS =
"#{CHAR_LCASE}#{CHAR_UCASE}"
CHAR_ALPHANUMERIC =
"#{CHAR_LETTERS}#{CHAR_DIGITS}"

Instance Method Summary collapse

Constructor Details

#initialize(configured_codecs = nil) ⇒ Encoder

Create the encoder, optionally pass in a list of codecs to use



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/codec/encoder.rb', line 62

def initialize(configured_codecs = nil)
  # codec list
  @codecs = []
  # default codecs
  @html_codec = Owasp::Esapi::Codec::HtmlCodec.new
  @percent_codec = Owasp::Esapi::Codec::PercentCodec.new
  @js_codec = Owasp::Esapi::Codec::JavascriptCodec.new
  @vb_codec = Owasp::Esapi::Codec::VbScriptCodec.new
  @css_codec = Owasp::Esapi::Codec::CssCodec.new
  @xml_codec = Owasp::Esapi::Codec::XmlCodec.new
  unless configured_codecs.nil?
    configured_codecs.each do |c|
      @codecs << c
    end
  else
    # setup some defaults codecs
    @codecs << @html_codec
    @codecs << @percent_codec
    @codecs << @js_codec
  end
end

Instance Method Details

#canonicalize(input) ⇒ Object

This method is equivalent to calling sanitize(input, true)



85
86
87
88
89
90
91
# File 'lib/codec/encoder.rb', line 85

def canonicalize(input)
  # if the input is nil, just return nil
  return nil if input.nil?

  # check teh ESAPI config and figure out if we want strict encoding
  sanitize(input,Owasp::Esapi.security_config.ids?)
end

#decode_for_base64(input) ⇒ Object

Decode data encoded with BASE-64 encoding. it assumes url safe encoding sets



171
172
173
174
# File 'lib/codec/encoder.rb', line 171

def decode_for_base64(input)
  return nil if input.nil?
  Base64.urlsafe_decode64(input)
end

#decode_for_url(input) ⇒ Object

Decode from URL. First canonicalize and detect any double-encoding. If this check passes, then the data is decoded using URL decoding.



190
191
192
193
194
# File 'lib/codec/encoder.rb', line 190

def decode_for_url(input)
  return nil if input.nil?
  clean = sanitize(input)
  CGI::unescape(input,Owasp::Esapi.security_config.encoding)
end

#dencode_for_html(input) ⇒ Object

Decodes HTML entities.



236
237
238
239
# File 'lib/codec/encoder.rb', line 236

def dencode_for_html(input)
  return nil if input.nil?
  @html_codec.decode(input)
end

#encode_for_base64(input) ⇒ Object

Encode for Base64. using the url safe input set



164
165
166
167
# File 'lib/codec/encoder.rb', line 164

def encode_for_base64(input)
  return nil if input.nil?
  Base64.urlsafe_encode64(input)
end

#encode_for_css(input) ⇒ Object

Encode data for use in Cascading Style Sheets (CSS) content. CSS Syntax (w3.org)



198
199
200
201
# File 'lib/codec/encoder.rb', line 198

def encode_for_css(input)
  return nil if input.nil?
  @css_codec.encode(IMMUNE_CSS,input)
end

#encode_for_dn(input) ⇒ Object



178
179
# File 'lib/codec/encoder.rb', line 178

def encode_for_dn(input)
end

#encode_for_html(input) ⇒ Object

Encode data for use in HTML using HTML entity encoding <p> Note that the following characters: 00-08, 0B-0C, 0E-1F, and 7F-9F cannot be used in HTML.



230
231
232
233
# File 'lib/codec/encoder.rb', line 230

def encode_for_html(input)
  return nil if input.nil?
  @html_codec.encode(IMMUNE_HTML,input)
end

#encode_for_html_attr(input) ⇒ Object

Encode data for use in HTML attributes.



242
243
244
245
# File 'lib/codec/encoder.rb', line 242

def encode_for_html_attr(input)
  return nil if input.nil?
  @html_codec.encode(IMMUNE_HTMLATTR,input)
end

#encode_for_javascript(input) ⇒ Object

Encode data for insertion inside a data value or function argument in JavaScript. Including user data directly inside a script is quite dangerous. Great care must be taken to prevent including user data directly into script code itself, as no amount of encoding will prevent attacks there.

Please note there are some JavaScript functions that can never safely receive untrusted data as input – even if the user input is encoded.

For example:

<script>
window.setInterval('<%= EVEN IF YOU ENCODE UNTRUSTED DATA YOU ARE XSSED HERE %>');
</script>


216
217
218
219
# File 'lib/codec/encoder.rb', line 216

def encode_for_javascript(input)
  return nil if input.nil?
  @js_codec.encode(IMMUNE_JAVASCRIPT,input)
end

#encode_for_ldap(input) ⇒ Object



176
177
# File 'lib/codec/encoder.rb', line 176

def encode_for_ldap(input)
end

#encode_for_os(codec, input) ⇒ Object

Encode for an operating system command shell according to the configured OS codec

Please note the following recommendations before choosing to use this method:

  1. It is strongly recommended that applications avoid making direct OS system calls if possible as such calls are not portable, and they are potentially unsafe. Please use language provided features if at all possible, rather than native OS calls to implement the desired feature.

  2. If an OS call cannot be avoided, then it is recommended that the program to be invoked be invoked directly (e.g., Kernel.system(“nameofcommand”,“parameterstocommand”)) as this avoids the use of the command shell. The “parameterstocommand” should of course be validated before passing them to the OS command.

  3. If you must use this method, then we recommend validating all user supplied input passed to the command shell as well, in addition to using this method in order to make the command shell invocation safe.

An example use of this method would be: Kernel.system(“dir” ,encode_for_os(WindowsCodec, “parameter(s)tocommandwithuserinput”);



256
257
258
259
# File 'lib/codec/encoder.rb', line 256

def encode_for_os(codec,input)
  return nil if input.nil?
  codec.encode(IMMUNE_OS,input)
end

#encode_for_sql(codec, input) ⇒ Object

Encode input for use in a SQL query, according to the selected codec (appropriate codecs include the MySQLCodec and OracleCodec).

This method is not recommended. The use of the PreparedStatement interface is the preferred approach. However, if for some reason this is impossible, then this method is provided as a weaker alternative.

The best approach is to make sure any single-quotes are double-quoted.



280
281
282
283
# File 'lib/codec/encoder.rb', line 280

def encode_for_sql(codec,input)
  return nil if input.nil?
  codec.encode(IMMUNE_SQL,input)
end

#encode_for_url(input) ⇒ Object

Encode for use in a URL. This method performs URL encoding on the entire string.



183
184
185
186
# File 'lib/codec/encoder.rb', line 183

def encode_for_url(input)
  return nil if input.nil?
  CGI::escape(input)
end

#encode_for_vbscript(input) ⇒ Object

Encode data for insertion inside a data value in a Visual Basic script. Putting user data directly inside a script is quite dangerous. Great care must be taken to prevent putting user data directly into script code itself, as no amount of encoding will prevent attacks there.

This method is not recommended as VBScript is only supported by Internet Explorer



266
267
268
269
# File 'lib/codec/encoder.rb', line 266

def encode_for_vbscript(input)
  return nil if input.nil?
  @vb_codec.encode(IMMUNE_VBSCRIPT,input)
end

#encode_for_xml(input) ⇒ Object

Encode data for use in an XML element. The implementation should follow the XML Encoding Standard from the W3C. <p> The use of a real XML parser is strongly encouraged. However, in the hopefully rare case that you need to make sure that data is safe for inclusion in an XML document and cannot use a parse, this method provides a safe mechanism to do so.



311
312
313
314
# File 'lib/codec/encoder.rb', line 311

def encode_for_xml(input)
  return nil if input.nil?
  @xml_codec.encode(IMMUNE_XML,input)
end

#encode_for_xml_attr(input) ⇒ Object

Encode data for use in an XML attribute. The implementation should follow the XML Encoding Standard from the W3C. <p> The use of a real XML parser is highly encouraged. However, in the hopefully rare case that you need to make sure that data is safe for inclusion in an XML document and cannot use a parse, this method provides a safe mechanism to do so.



323
324
325
326
# File 'lib/codec/encoder.rb', line 323

def encode_for_xml_attr(input)
  return nil if input.nil?
  @xml_codec.encode(IMMUNE_XMLATTR,input)
end

#encode_for_xpath(input) ⇒ Object

Encode data for use in an XPath query.

NB: The reference implementation encodes almost everything and may over-encode.

The difficulty with XPath encoding is that XPath has no built in mechanism for escaping characters. It is possible to use XQuery in a parameterized way to prevent injection.

For more information, refer to this article which specifies the following list of characters as the most dangerous: ^&“*‘;<>().

This paper suggests disallowing ‘ and “ in queries.<p>



299
300
301
302
# File 'lib/codec/encoder.rb', line 299

def encode_for_xpath(input)
  return nil if input.nil?
  @xml_codec.encode(IMMUNE_XPATH,input)
end

#sanitize(input, strict) ⇒ Object

Sanitization is simply the operation of reducing a possibly encoded string down to its simplest form. This is important, because attackers frequently use encoding to change their input in a way that will bypass validation filters, but still be interpreted properly by the target of the attack. Note that data encoded more than once is not something that a normal user would generate and should be regarded as an attack. Everyone says you shouldn’t do validation without canonicalizing the data first. This is easier said than done. The canonicalize method can be used to simplify just about any input down to its most basic form. Note that sanitization doesn’t handle Unicode issues, it focuses on higher level encoding and escaping schemes. In addition to simple decoding, sanitize also handles:

  • Perverse but legal variants of escaping schemes

  • Multiple escaping (%2526 or &#x26;lt;)

  • Mixed escaping (%26lt;)

  • Nested escaping (%%316 or &%6ct;)

  • All combinations of multiple, mixed, and nested encoding/escaping (%2&#x35;3c or &#x2526gt;)

Although ESAPI is able to canonicalize multiple, mixed, or nested encoding, it’s safer to not accept this stuff in the first place. In ESAPI, the default is “strict” mode that throws an IntrusionException if it receives anything not single-encoded with a single scheme. Even if you disable “strict” mode, you’ll still get warning messages in the log about each multiple encoding and mixed encoding received.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/codec/encoder.rb', line 115

def sanitize(input, strict)
  # check input again, as someone may just wana call sanitize
  return nil if input.nil?
  working = input
  found_codec = nil
  mixed_count = 1
  found_count = 0
  clean = false
  while !clean
    clean = true
    @codecs.each do |codec|
      old = working
      working = codec.decode(working)
      if !old.eql?(working)
        if !found_codec.nil? and found_codec != codec
          mixed_count += 1
        end
        found_codec = codec
        if clean
          found_count += 1
        end
        clean = false
      end
    end
  end
  # test for strict encoding, and indicate mixed and multiple errors
  if found_count >= 2 and mixed_count > 1
    if strict
      raise Owasp::Esapi::IntrustionException.new("Input validation failure", "Multiple (#{found_count}x) and mixed encoding (#{mixed_count}x) detected in #{input}")
    else
      Owasp::Esapi.logger.warn("Multiple (#{found_count}x) and mixed encoding (#{mixed_count}x) detected in #{input}")
    end
  elsif found_count >= 2
    if strict
      raise Owasp::Esapi::IntrustionException.new("Input validation failure", "Multiple (#{found_count}x) detected in #{input}")
    else
      Owasp::Esapi.logger.warn("Multiple (#{found_count}x) detected in #{input}")
    end
  elsif mixed_count > 1
    if strict
      raise Owasp::Esapi::IntrustionException.new("Input validation failure", "Mixed encoding (#{mixed_count}x) detected in #{input}")
    else
      Owasp::Esapi.logger.warn("Mixed encoding (#{mixed_count}x) detected in #{input}")
    end
  end
  working
end