580 lines
No EOL
31 KiB
XML
580 lines
No EOL
31 KiB
XML
<?xml version='1.0' encoding='utf-8' ?>
|
|
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
|
|
<!ENTITY % BOOK_ENTITIES SYSTEM "Secure_Ruby_Development_Guide.ent">
|
|
%BOOK_ENTITIES;
|
|
]>
|
|
<chapter id="chap-Secure_Ruby_Development_Guide-Language_features">
|
|
<title>Language features</title>
|
|
<para>
|
|
</para>
|
|
<section>
|
|
<title>Tainting and restricted code execution</title>
|
|
<para>
|
|
Ruby language includes a security mechanism to handle untrusted objects
|
|
and restrict arbitrary code execution. This mechanism consists of two parts: first is an automated way of marking objects in Ruby as coming from untrusted source, called tainting. The second part is mechanism for restricting code execution and prevents certain potentially dangerous functions being executed on tainted data. Ruby interpreter can run in several safe levels, each of which defines different restrictions.
|
|
</para>
|
|
<para>
|
|
This mechanism (especially restricted code execution) is implementation
|
|
specific and is not part of Ruby specification. Other Ruby implementations such as Rubinius and JRuby do not implement safe levels. However, taint flag is part of the rubyspec.
|
|
</para>
|
|
<section>
|
|
<title>Object.tainted?</title>
|
|
<para>
|
|
Each object in Ruby carries a taint flag which marks it as originating from unsafe source. Additionally, any object derived from tainted object is also tainted. Objects that come from external environment are automatically marked as tainted, which includes command line arguments (<constant>ARGV</constant>), environment variables (<constant>ENV</constant>), data read from files, sockets or other streams. Environment variable <constant>PATH</constant> is exception: it is tainted only if it contains a world-writable directory.
|
|
</para>
|
|
<para>
|
|
To check whether object is tainted and change taintedness of object, use methods <command>Object.tainted?</command>, <command>Object.taint</command> and <command>Object.untaint</command>:
|
|
|
|
<programlisting>
|
|
>> input = gets
|
|
exploitable
|
|
=> "exploitable\n"
|
|
>> input.tainted?
|
|
=> true
|
|
>> input.untaint
|
|
=> "exploitable\n"
|
|
>> input.tainted?
|
|
=> false
|
|
</programlisting>
|
|
<note>
|
|
<para>
|
|
Literals (such as numbers or symbols) are exception: they do not carry taint flag and are always untainted.
|
|
</para>
|
|
</note>
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>Object.untrusted?</title>
|
|
<para>
|
|
At higher safe levels (see safe level 4 below) any code is automatically untrusted and interpreter prevents execution of untrusted code on trusted objects. In Ruby 1.8, taint flag is also used to mark objects as untrusted, so untrusted code is not allowed to modify untainted objects. In addition, any object created by untrusted code is tainted. This effectively allows to sandbox an untrusted code, which will not be allowed to modify "trusted" objects.
|
|
</para>
|
|
<para>
|
|
Mixing taint and trust of object has serious drawback - untrusted code is allowed to modify all tainted objects (even if they come from trusted code).
|
|
</para>
|
|
<para>
|
|
Ruby 1.9 adds another flag to each object to mark it as untrusted. Untrusted code is now allowed only to modify untrusted objects (ignoring taint flag), and objects created by untrusted code are automatically marked as untrusted and tainted. To check and modify trust flag use methods <command>Object.untrusted?</command>, <command>Object.untrust</command> and <command>Object.trust</command>.
|
|
</para>
|
|
<para>
|
|
However, Ruby 2.1 deprecates trust flag and the behaviour of above methods is the same as <command>Object.tainted?</command>, <command>Object.taint</command> and <command>Object.untaint</command>. This change comes together with removal of safe level 4, which makes trust flag useless (see <ulink url="https://bugs.ruby-lang.org/issues/8468">issue on ruby-lang</ulink> or read below).
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>$SAFE</title>
|
|
<para>
|
|
Ruby interpreter can run in restricted execution mode with several levels of checking, controlled by global variable <constant>$SAFE</constant>. There are 5 possible levels: 0,1,2,3,4 with 0 being default safe level. <constant>$SAFE</constant> is thread-local and its value can only be increased (at least in theory - in practice there are well known ways how to work around restricted code execution or decrease a safe level. See <xref linkend='SAFE-security'/>). Safe level can be changed by assigning to <constant>$SAFE</constant> or with <command>-T<level></command> argument.
|
|
</para>
|
|
|
|
<para>Safe levels have following restrictions:
|
|
<variablelist>
|
|
<varlistentry>
|
|
<term>
|
|
level 0
|
|
</term>
|
|
<listitem>
|
|
<para>strings from streams/environment/ARGV are tainted (default)</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
|
|
<varlistentry>
|
|
<term>
|
|
level 1
|
|
</term>
|
|
<listitem>
|
|
<para>dangerous operations on tainted values are forbidden (such as <command>eval</command>, <command>require</command> etc.)</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
|
|
<varlistentry>
|
|
<term>
|
|
level 2
|
|
</term>
|
|
<listitem>
|
|
<para>adds to the level 1 also restrictions on directory, file and process operations</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
|
|
<varlistentry>
|
|
<term>
|
|
level 3
|
|
</term>
|
|
<listitem>
|
|
<para>in addition all created objects are tainted and untrusted</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
|
|
<varlistentry>
|
|
<term>
|
|
level 4
|
|
</term>
|
|
<listitem>
|
|
<para>code running in this level cannot change trusted objects, direct output is also restricted. This safe level <ulink url="https://bugs.ruby-lang.org/issues/8468">is deprecated</ulink> since Ruby 2.1</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
</variablelist>
|
|
</para>
|
|
<para>
|
|
There is a lack of documentation of what is restricted in each safe level. For more exhausting description refer to <ulink url="http://ruby-doc.com/docs/ProgrammingRuby/">Programming Ruby: Pragmatic programmer`s guide</ulink>.
|
|
</para>
|
|
<section id='SAFE-security'>
|
|
<title id='SAFE-security.title'>Security considerations of $SAFE</title>
|
|
<para>
|
|
Design of restricted code execution based on <constant>$SAFE</constant> is inherently flawed. Blacklist approach is used to restrict operation on each level, which means any missed function creates a vulnerability. In past several security updates were related to restricted code execution and taint flag (see <ulink url="https://www.ruby-lang.org/en/news/2005/10/03/ruby-vulnerability-in-the-safe-level-settings/">CVE-2005-2337</ulink>, CVE-2006-3694, <ulink url="https://www.ruby-lang.org/en/news/2008/08/08/multiple-vulnerabilities-in-ruby/">CVE-2008-3655</ulink>, <ulink url="https://www.ruby-lang.org/en/news/2008/08/08/multiple-vulnerabilities-in-ruby/">CVE-2008-3657</ulink>, <ulink url="https://www.ruby-lang.org/en/news/2011/02/18/exception-methods-can-bypass-safe/">CVE-2011-1005</ulink>, <ulink url="https://www.ruby-lang.org/en/news/2012/10/12/cve-2012-4464-cve-2012-4466/">CVE-2012-4464</ulink>,<ulink url="https://www.ruby-lang.org/en/news/2012/10/12/cve-2012-4464-cve-2012-4466/">CVE-2012-4466</ulink> and <ulink url="https://www.ruby-lang.org/en/news/2013/05/14/taint-bypass-dl-fiddle-cve-2013-2065/">CVE-2013-2065</ulink>).
|
|
</para>
|
|
|
|
<warning>
|
|
<para>
|
|
Design of restricted code execution based on <constant>$SAFE</constant> is inherently flawed and cannot be used to run untrusted code even at the highest safe level. It must not be used as mechanism to create a secure sandbox, as attacker will be able to work around the restrictions or decrease safe level.
|
|
</para>
|
|
</warning>
|
|
|
|
<para>
|
|
One example of how exploitable the design is comes from <ulink url="https://www.ruby-lang.org/en/news/2013/05/14/taint-bypass-dl-fiddle-cve-2013-2065/">CVE-2013-2065</ulink>:
|
|
|
|
<programlisting language="Ruby">
|
|
require 'fiddle'
|
|
|
|
$SAFE = 1
|
|
input = "uname -rs".taint
|
|
handle = DL.dlopen(nil)
|
|
sys = Fiddle::Function.new(handle['system'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
|
|
sys.call DL::CPtr[input].to_i
|
|
</programlisting>
|
|
|
|
Even though safe level 1 should restrict execution of system commands, this can be bypassed using Fiddle library, which is an extension to translate a foreign function interface with Ruby. Exploit above bypasses safe level by passing input to system call as numeric memory offset. Since numbers as literals cannot be tainted, code cannot check taintedness of input.
|
|
</para>
|
|
|
|
<note>
|
|
<para>
|
|
However, running application with higher safe level is still useful for catching unintended programming errors, such as executing <command>eval</command> on tainted string.
|
|
</para>
|
|
</note>
|
|
</section>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Dangerous methods</title>
|
|
<para>
|
|
Ruby contains number of methods and modules that should be used with caution, since calling them with input potentially controlled by attacker might be abused into arbitrary code execution. These include:
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><command>Kernel#exec</command>, <command>Kernel#system</command>, backticks and <command>%x{...}</command></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><command>Kernel#fork</command>, <command>Kernel#spawn</command></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><command>Kernel#load</command>, <command>Kernel#autoload</command></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><command>Kernel#require</command>, <command> Kernel#require_relative</command></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><command>DL</command> and <command>Fiddle</command> module</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><command>Object#send</command>, <command>Object#__send__</command> and <command>Object#public_send</command></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><command>BasicObject#instance_eval</command>, <command>BasicObject#instance_exec</command></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><command>Module#class_eval</command>, <command>Module#class_exec</command>, <command>Module#module_eval</command>, <command>Module#module_exec</command></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><command>Module#alias_method</command></para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
</section>
|
|
|
|
<section id="RubySymbols">
|
|
<title>Symbols</title>
|
|
<para>Symbols in MRI Ruby are used for method, variable and constant lookup. They are implemented as integers so that they are faster to look up in hastables. Once symbol is created, memory allocated for it is never freed. This creates opportunity for attacker: if he is able to create arbitrary symbols, he could flood the application with unique symbols that will never be garbage collected. Memory consumption of Ruby process would only grow until it runs out of memory, resulting in Denial of Service attack.</para>
|
|
<para>
|
|
Application developers should be careful when calling <command>to_sym</command> or <command>intern</command> on user-supplied strings. Additionally, other methods may convert supplied arguments to symbols internally, for example <command>Object.send</command>, <command>Object.instance_variable_set</command>, <command>Object.instance_variable_get</command>, <command>Module.const_get</command> or <command>Module.const_set</command>:
|
|
<programlisting language="Ruby">
|
|
>> Symbol.all_symbols.size
|
|
=> 2956
|
|
>> Module.const_get('MY_SYMBOL')
|
|
NameError: uninitialized constant Module::MY_SYMBOL
|
|
>> Symbol.all_symbols.size
|
|
=> 2957
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
Array of all currently defined symbols is available through <command>Symbol.all_symbols</command> class method.
|
|
</para>
|
|
</section>
|
|
|
|
|
|
<section>
|
|
<title>Serialization in Ruby</title>
|
|
<para>
|
|
Deserialization of untrusted data has been on the top of critical vulnerabilities in 2013 (prominent examples are deserialization issues found in Ruby on Rails, see <ulink url="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-0156">CVE-2013-0156</ulink>, <ulink url="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-0277">CVE-2013-0277</ulink> or <ulink url="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-0333">CVE-2013-0333</ulink>). There are several ways how to serialize objects in Ruby:
|
|
</para>
|
|
<section>
|
|
<title>Marshal.load</title>
|
|
<para>
|
|
<command>Marshal.dump</command> and <command>Marshal.load</command> can serialize and deserialize most of the classes in Ruby. If application deserializes data from untrusted source, attacker can abuse this to execute arbitrary code. Therefore, this method is not suitable most of the time and should never be be used on data from unstrusted source.
|
|
</para>
|
|
|
|
</section>
|
|
|
|
<section>
|
|
<title>YAML.load</title>
|
|
<para>
|
|
YAML is a popular serialization format among Ruby developers. Just like <command>Marshal.load</command> it can be used to deserialize most of the Ruby classes and also should never be used on untrusted data.
|
|
</para>
|
|
|
|
<section>
|
|
<title>SafeYAML</title>
|
|
<para>
|
|
Alternative approach is taken by <ulink url="http://danieltao.com/safe_yaml/">SafeYAML</ulink> gem - by default it allows deserialization of only few types of objects that can be considered safe, such as <constant>Hash</constant>, <constant>Array</constant>, <constant>String</constant> etc. When application requires serialization of certain types, developer can explicitly whitelist trusted types of objects:
|
|
<programlisting>
|
|
SafeYAML.whitelist!(FrobDispenser, GobbleFactory)
|
|
</programlisting>
|
|
This approach is more versatile, since it disables serialization of unsafe classes, yet allows developer to serialize know benign object. Requiring <constant>safe_yaml</constant> will patch method <command>YAML.load</command>.
|
|
</para>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>JSON.parse and JSON.load</title>
|
|
<para>
|
|
JSON format supports only several primitive data types such as strings, arrays, hashes, numbers etc. This certainly limits the attack surface, but it should not give developer false sense of security - one example is <ulink url="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-0333">CVE-2013-0333</ulink> vulnerability in Ruby on Rails, when parser used for deserialization of JSON data actually converted data to a subset of <constant>YAML</constant> and used <command>YAML.load</command> to deserialize.
|
|
</para>
|
|
|
|
<para>
|
|
However, it is possible to extend Ruby classes to be JSON-dumpable:
|
|
|
|
<programlisting language="Ruby">
|
|
class Range
|
|
def to_json(*a)
|
|
{
|
|
'json_class' => self.class.name,
|
|
'data' => [ first, last, exclude_end? ]
|
|
}.to_json(*a)
|
|
end
|
|
|
|
def self.json_create(o)
|
|
new(*o['data'])
|
|
end
|
|
end
|
|
</programlisting>
|
|
|
|
This will allow instances of Range class to be serialized with JSON:
|
|
|
|
<programlisting language="Ruby">
|
|
>> (1..10).to_json
|
|
=> "{\"json_class\":\"Range\",\"data\":[1,10,false]}"
|
|
</programlisting>
|
|
|
|
During deserialization, JSON gem will try to look up class referenced by "json_class", which might create new Symbol if the class does not exist, possibly allowing Denial of Service (see <xref linkend='RubySymbols'/>):
|
|
<programlisting>
|
|
>> Symbol.all_symbols.size
|
|
=> 3179
|
|
>> JSON.parse('{"json_class":"NonexistentClass"}')
|
|
ArgumentError: can't get const NonexistentClass: uninitialized constant NonexistentClass
|
|
>> Symbol.all_symbols.size
|
|
=> 3180
|
|
</programlisting>
|
|
To disable this, <constant>:create_additions => false</constant> option can be passed as second argument:
|
|
|
|
<programlisting>
|
|
>> JSON.parse('{"json_class":"NonexistentClass"}',:create_additions => false)
|
|
=> {"json_class"=>"NonexistentClass"}
|
|
</programlisting>
|
|
|
|
This behaviour has changed in response to <ulink url="https://www.ruby-lang.org/en/news/2013/02/22/json-dos-cve-2013-0269/">CVE-2013-0269</ulink> and <command>JSON.parse</command> now defaults to <constant>:create_additions => false</constant>. However, default behaviour has not changed for <command>JSON.load</command>, which is dangerous to call on untrusted input.
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>Exploiting deserialization vulnerabilities</title>
|
|
<para>
|
|
To exploit deserialization vulnerability, there must already be a dangerous class loaded in the current namespace. In particular, it contains unsafe <command>init_with()</command> or <command>[]=</command> methods, that get called during deserialization. This might seem like an unlikely event, however, its very likely in case of big projects like Ruby on Rails.
|
|
</para>
|
|
<para>
|
|
<ulink url="https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/61bkgvnSGTQ">CVE-2013-0156</ulink> vulnerability in Ruby on Rails can be used as an example. A vulnerable class in this case was <constant>ActionDispatch::Routing::RouteSet::NamedRouteCollection</constant>, which contained code like this:
|
|
<programlisting language="Ruby">
|
|
class NamedRouteCollection
|
|
alias []= add
|
|
|
|
def add(name, route)
|
|
routes[name.to_sym] = route
|
|
define_named_route_methods(name, route)
|
|
end
|
|
|
|
def define_named_route_methods(name, route)
|
|
define_url_helper route, :"#{name}_path",
|
|
route.defaults.merge(:use_route => name, :only_path => true)
|
|
define_url_helper route, :"#{name}_url",
|
|
route.defaults.merge(:use_route => name, :only_path => false)
|
|
end
|
|
|
|
def define_url_helper(route, name, options)@module.module_eval <<-END_EVAL
|
|
def #{name}(*args)
|
|
# ... code
|
|
end
|
|
END_EVAL
|
|
end
|
|
|
|
...
|
|
</programlisting>
|
|
Even though <command>module_eval</command> is hidden under several layers of method calls, calling <command>[]=</command> effectively passes first argument to the <command>define_url_helper</command>, where it gets evaluated.
|
|
</para>
|
|
|
|
<para>
|
|
To exploit vulnerable class, it is enough to deserialize YAML payload below:
|
|
<programlisting>
|
|
--- !ruby/hash:NamedRouteCollection
|
|
foo; end; system 'rm /etc/passwd'; def bar: baz
|
|
</programlisting>
|
|
Before deserialization, Ruby's YAML parser Psych first looks at the declared type, which says this object is an instance of <constant>NamedRouteCollection</constant> and subclass of Ruby's <constant>Kernel::Hash</constant> class.
|
|
</para>
|
|
<para>
|
|
Deserialization of hashes from YAML to Ruby makes use of <command>[]=</command> method. Given YAML like
|
|
|
|
<programlisting>
|
|
--- !ruby/hash:MyHash
|
|
key1: value1
|
|
key2: value2
|
|
</programlisting>
|
|
deserialization process is equivalent to calling
|
|
<programlisting language="Ruby">
|
|
newobj = MyHash.new
|
|
newobj['key1'] = 'value1'
|
|
newobj['key2'] = 'value2'
|
|
newobj
|
|
</programlisting>
|
|
</para>
|
|
|
|
<para>
|
|
In the case of YAML payload, key and value pair is
|
|
<programlisting language="Ruby">
|
|
['foo; end; system 'rm /etc/passwd'; def bar','baz']
|
|
</programlisting>
|
|
so deserialization process will call <command>[]=</command> method on <constant>NamedRouteCollection</constant> with key <code>'foo; end; system 'rm /etc/passwd'; def bar'</code>.
|
|
</para>
|
|
<para>
|
|
This gets passed to <command>define_url_helper</command> as an argument and following code gets evaluated:
|
|
|
|
<programlisting language="Ruby">
|
|
def foo; end; system 'rm /etc/passwd'; def bar(*args)
|
|
# ... code
|
|
end
|
|
</programlisting>
|
|
Reordering the code above to be more readable, this is equivalent to
|
|
<programlisting language="Ruby">
|
|
def foo
|
|
end
|
|
|
|
system 'rm /etc/passwd'
|
|
|
|
def bar(*args)
|
|
# ... code
|
|
end
|
|
</programlisting>
|
|
</para>
|
|
<section>
|
|
<title>References</title>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Aaron Patterson's blog <ulink url="http://tenderlovemaking.com/2013/02/06/yaml-f7u12.html">http://tenderlovemaking.com/2013/02/06/yaml-f7u12.html</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Charlie Sommerville's blog <ulink url="https://charlie.bz/blog/rails-3.2.10-remote-code-execution">https://charlie.bz/blog/rails-3.2.10-remote-code-execution</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Metasploit's blog <ulink url="https://community.rapid7.com/community/metasploit/blog/2013/01/09/serialization-mischief-in-ruby-land-cve-2013-0156">https://community.rapid7.com/community/metasploit/blog/2013/01/09/serialization-mischief-in-ruby-land-cve-2013-0156</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Extending Hash <ulink url="http://www.yaml.org/YAML_for_ruby.html#extending_kernel::hash">http://www.yaml.org/YAML_for_ruby.html#extending_kernel::hash</ulink>
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Regular expressions</title>
|
|
<para>
|
|
A common gotcha in Ruby regular expressions relates to anchors marking the begninning and the end of a string. Specifically, <constant>^</constant> and <constant>$</constant> refer to the beginning and the end of a line, rather then a string. If regular expression like <command>/^[a-z]+$</command> is used to whitelist user input, attacker can bypass it by including newline. To match the beginning and the end of a string use anchors <command>\A</command> and <command>\z</command>.
|
|
<programlisting language="Ruby">
|
|
>> puts 'Exploited!' if /^benign$/ =~ "benign\n with exploit"
|
|
Exploited!
|
|
=> nil
|
|
>> puts 'Exploited!' if /\Abenign\z/ =~ "benign\n with exploit"
|
|
=> nil
|
|
</programlisting>
|
|
</para>
|
|
</section>
|
|
|
|
|
|
|
|
<section>
|
|
<title>Object.send</title>
|
|
<para>
|
|
<command>Object.send</command> is a method with serious security impact, since it invokes any method on object, including private methods. Some methods in Ruby like <command>eval</command> or <command>exit!</command> are private methods of <constant>Object</constant> and can be invoked using <command>send</command>:
|
|
<programlisting language="Ruby">
|
|
>> Object.private_methods.include?(:eval)
|
|
=> true
|
|
>> Object.private_methods.include?(:exit)
|
|
=> true
|
|
>> Object.send('eval', "system 'uname'")
|
|
Linux
|
|
=> true
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
Alternative is <command>Object.public_send</command>, which by definition only invokes public methods on object. However, this does not prevent attacker from executing only private methods, since <command>Object.send</command> itself is (and has to be) public:
|
|
<programlisting language="Ruby">
|
|
>> Object.public_send("send","eval","system 'uname'")
|
|
Linux
|
|
=> true
|
|
>> Object.public_send("send","exit!") # exits
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
Developers should be careful when invoking <command>send</command> and <command>public_send</command> with user controlled arguments.
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>SSL in Ruby</title>
|
|
<para>
|
|
Ruby uses OpenSSL implementation of common cryptographic primitives, which are accessible through <constant>OpenSSL</constant> module included in standard library. This module is then used by other parts of standard library to manage SSL, including <constant>Net::HTTP</constant>, <constant>Net::POP</constant>, <constant>Net::IMAP</constant>, <constant>Net::SMTP</constant> and others.
|
|
</para>
|
|
|
|
<para>
|
|
There are four valid verification modes <constant>VERIFY_NONE</constant>, <constant>VERIFY_PEER</constant>, <constant>VERIFY_FAIL_IF_NO_PEER_CERT</constant> and <constant>VERIFY_CLIENT_ONCE</constant>. These correspond to underlying <ulink url="https://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html#NOTES">OpenSSL modes</ulink>.
|
|
</para>
|
|
|
|
<para>
|
|
SSL connection can be created using OpenSSL module directly:
|
|
<programlisting language="Ruby">
|
|
>> require 'openssl'
|
|
=> true
|
|
>> require 'socket'
|
|
=> true
|
|
>> tcp_client = TCPSocket.new 'redhat.com', 443
|
|
=> #<TCPSocket:fd 5>
|
|
>> ssl_context = OpenSSL::SSL::SSLContext.new
|
|
=> #<OpenSSL::SSL::SSLContext:0x00000000fcf918>
|
|
>> ssl_context.set_params
|
|
=> {:ssl_version=>"SSLv23", :verify_mode=>1, :ciphers=>"ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW", :options=>-2147480585}
|
|
>> ssl_client = OpenSSL::SSL::SSLSocket.new tcp_client, ssl_context
|
|
=> #<OpenSSL::SSL::SSLSocket:0x0000000106a418>
|
|
>> ssl_client.connect
|
|
=> #<OpenSSL::SSL::SSLSocket:0x0000000106a418>
|
|
</programlisting>
|
|
Note the call to <command>ssl_context.set_params</command>: by default, when context is created, all its instance variables are nil. Before using the context, <command>set_params</command> should be called to initialize them (when called without argument, default parameters are chosen). In case this call is omitted and variables are left uninitialized, certificate verification is not performed (effectively the same as <constant>VERIFY_NONE</constant> mode). Default parameters are stored in the constant:
|
|
<programlisting language="Ruby">
|
|
>> OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
|
=> {:ssl_version=>"SSLv23", :verify_mode=>1, :ciphers=>"ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW", :options=>-2147480585}
|
|
</programlisting>
|
|
One of the side effects of <command>set_params</command> is that it also sets up certificate store with certificates from default certificate area (see <xref linkend='CertificateStore'/> below):
|
|
<programlisting language="Ruby">
|
|
>> ssl_context.cert_store
|
|
=> nil
|
|
>> ssl_context.set_params
|
|
=> {:ssl_version=>"SSLv23", :verify_mode=>1, :ciphers=>"ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW", :options=>-2147480585}
|
|
>> ssl_context.cert_store
|
|
=> #<OpenSSL::X509::Store:0x00000000fea740>
|
|
</programlisting>
|
|
</para>
|
|
<section id="CertificateStore">
|
|
<title>Certificate store</title>
|
|
<para>
|
|
Class <command>OpenSSL::X509::Store</command> implements certificate store in Ruby. Certificate store is similar to store in web browsers - it contains trusted certificates that can be used to verify certificate chain. When new certificate store is created, it contains no trusted certificates by default.
|
|
</para>
|
|
<para>
|
|
To populate certificate store with certificates, use one of methods:
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<command>Store#add_file</command> takes a path to DER/PEM encoded certificate
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<command>Store#add_cert</command> takes instance of <constant>X509::Certificate</constant>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<command>Store#add_path</command> takes a path to a directory with trusted certificates
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<command>Store#set_default_path</command> adds certificates stored in default certificate area
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
<para>
|
|
OpenSSL installation usually creates a directory, which stores several trusted certificates (approach similar to web browsers, that also come with predefined certificate store). To populate certificate store with certificates that come with OpenSSL use <command>Store#set_default_path</command>. The path to default certificate area is defined as:
|
|
<programlisting language="Ruby">
|
|
>> OpenSSL::X509::DEFAULT_CERT_AREA
|
|
=> "/etc/pki/tls"
|
|
</programlisting>
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>Ruby libraries using OpenSSL</title>
|
|
<para>
|
|
There are several libraries that build on top of OpenSSL. Depending on how a library uses <constant>SSLContext</constant>, users may encounter exception from OpenSSL code saying the certificate verification failed:
|
|
<programlisting language="Ruby">
|
|
>> ssl_client.connect
|
|
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
|
|
from (irb):7:in `connect'
|
|
from (irb):7
|
|
</programlisting>
|
|
This usually happens when <constant>verify_mode</constant> is set to check the certificate, but the certificate store used does not contain trusted certificate required to verify the SSL sent by the server.
|
|
</para>
|
|
<note>
|
|
<para>
|
|
The worst advice that can be found on internet on how to fix SSL is to set
|
|
<programlisting>
|
|
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
|
|
</programlisting>
|
|
This redefines constant <constant>OpenSSL::SSL::VERIFY_PEER</constant> to have the same effect as <constant>OpenSSL::SSL::VERIFY_PEER</constant>, effectively globally disabling certificate checking.
|
|
</para>
|
|
</note>
|
|
<para>
|
|
Take <constant>Net::IMAP</constant> as example (the code below refers to Ruby 1.9.3): initialize method for creating a new IMAP connection has takes the following arguments:
|
|
<programlisting language="Ruby">
|
|
def initialize(host, port_or_options = {},
|
|
usessl = false, certs = nil, verify = true)
|
|
...
|
|
</programlisting>
|
|
When SSL connection is used but <constant>certs</constant> and <constant>verify</constant> arguments are left to be assigned defaults values, SSLError may be thrown when certificate sent by server cannot be verified.
|
|
</para>
|
|
<important>
|
|
<para>
|
|
The correct solution is to always make sure certificate store used by <constant>SSLContext</constant> contains a trusted certificate that can be used to verify the certificate sent by the server.
|
|
</para>
|
|
</important>
|
|
<section>
|
|
<title>Behaviour in different Ruby versions</title>
|
|
<para>
|
|
Default behaviour differs across Ruby versions: in Ruby 1.8, SSL enabled libraries usually falled back to <constant>VERIFY_NONE</constant> mode. The above mentioned <constant>Net::IMAP#initialize</constant> looks like this:
|
|
<programlisting language="Ruby">
|
|
def initialize(host, port = PORT, usessl = false, certs = nil, verify = false)
|
|
...
|
|
</programlisting>
|
|
Starting from Ruby 1.9, standard library defaults to <constant>VERIFY_PEER</constant> mode.
|
|
</para>
|
|
</section>
|
|
</section>
|
|
</section>
|
|
</chapter> |