diff --git a/lib/brakeman/checks/check_i18n_xss.rb b/lib/brakeman/checks/check_i18n_xss.rb new file mode 100644 index 0000000000000000000000000000000000000000..5c31de6ed807f786bdc9d1bd9c6615baf540f446 --- /dev/null +++ b/lib/brakeman/checks/check_i18n_xss.rb @@ -0,0 +1,49 @@ +require 'brakeman/checks/base_check' + +class Brakeman::CheckI18nXSS < Brakeman::BaseCheck + Brakeman::Checks.add self + + @description = "Checks for i18n XSS (CVE-2013-4491)" + + def run_check + if (version_between? "3.0.6", "3.2.15" or version_between? "4.0.0", "4.0.1")# and not has_workaround? + message = "Rails #{tracker.config[:rails_version]} has an XSS vulnerability in i18n (CVE-2013-4491). Upgrade to Rails version " + + i18n_gem = tracker.config[:gems] && tracker.config[:gems][:i18n] + + if version_between? "3.0.6", "3.1.99" and version_before i18n_gem, "0.5.1" + message << "3.2.16 or i18n 0.5.1" + elsif version_between? "3.2.0", "4.0.1" and version_before i18n_gem, "0.6.6" + message << "4.0.2 or i18n 0.6.6" + else + return + end + + warn :warning_type => "Cross Site Scripting", + :warning_code => :CVE_2013_4491, + :message => message, + :confidence => CONFIDENCE[:med], + :file => gemfile_or_environment, + :link_path => "https://groups.google.com/d/msg/ruby-security-ann/pLrh6DUw998/bLFEyIO4k_EJ" + end + end + + def version_before gem_version, target + return true unless gem_version + gem_version.split('.').map(&:to_i).zip(target.split('.').map(&:to_i)).each do |gv, t| + if gv < t + return true + elsif gv > t + return false + end + end + + false + end + + def has_workaround? + tracker.check_initializers(:I18n, :const_defined?).any? do |match| + match.last.first_arg == s(:lit, :MissingTranslation) + end + end +end diff --git a/lib/brakeman/processors/gem_processor.rb b/lib/brakeman/processors/gem_processor.rb index 66625ceb03417702ec44ac9889b9247b65de7ff0..d0471219aee2b687faca1c055073750eaeb6aff5 100644 --- a/lib/brakeman/processors/gem_processor.rb +++ b/lib/brakeman/processors/gem_processor.rb @@ -15,6 +15,7 @@ class Brakeman::GemProcessor < Brakeman::BaseProcessor if gem_lock get_rails_version gem_lock get_json_version gem_lock + get_i18n_version gem_lock elsif @tracker.config[:gems][:rails] =~ /(\d+.\d+.\d+)/ @tracker.config[:rails_version] = $1 end @@ -61,4 +62,8 @@ class Brakeman::GemProcessor < Brakeman::BaseProcessor @tracker.config[:gems][:json] = get_version("json", gem_lock) @tracker.config[:gems][:json_pure] = get_version("json_pure", gem_lock) end + + def get_i18n_version gem_lock + @tracker.config[:gems][:i18n] = get_version("i18n", gem_lock) + end end diff --git a/lib/brakeman/warning_codes.rb b/lib/brakeman/warning_codes.rb index 589272b50f2d679129377b72d949b57c948d2066..87044163f524e4ade241ce612378321d55572145 100644 --- a/lib/brakeman/warning_codes.rb +++ b/lib/brakeman/warning_codes.rb @@ -62,7 +62,8 @@ module Brakeman::WarningCodes :unsafe_symbol_creation => 59, :dangerous_attr_accessible => 60, :local_request_config => 61, - :detailed_exceptions => 62 + :detailed_exceptions => 62, + :CVE_2013_4491 => 63, } def self.code name diff --git a/test/apps/rails4/config/initializers/i18n.rb b/test/apps/rails4/config/initializers/i18n.rb new file mode 100644 index 0000000000000000000000000000000000000000..d9b10bbad710d5c5a26fb239ae81046ab5fb9860 --- /dev/null +++ b/test/apps/rails4/config/initializers/i18n.rb @@ -0,0 +1,20 @@ +require 'i18n' + +# Override exception handler to more carefully html-escape missing-key results. +class HtmlSafeI18nExceptionHandler + Missing = I18n.const_defined?(:MissingTranslation) ? I18n::MissingTranslation : I18n::MissingTranslationData + + def initialize(original_exception_handler) + @original_exception_handler = original_exception_handler + end + + def call(exception, locale, key, options) + if exception.is_a?(Missing) && options[:rescue_format] == :html + keys = exception.keys.map { |k| Rack::Utils.escape_html k } + key = keys.last.to_s.gsub('_', ' ').gsub(/\b('?[a-z])/) { $1.capitalize } + %(#{key}) + else + @original_exception_handler.call(exception, locale, key, options) + end + end +end diff --git a/test/tests/only_files_option.rb b/test/tests/only_files_option.rb index dd86f6f62d2f0932ee6a790dba12ecb87ab04221..1dc012f23e18bae81be8600de35fe13d1b3ff1e8 100644 --- a/test/tests/only_files_option.rb +++ b/test/tests/only_files_option.rb @@ -53,4 +53,13 @@ class OnlyFilesOptionTests < Test::Unit::TestCase :file => /sanitized\.html\.erb/ end + def test_i18n_xss_CVE_2013_4491 + assert_warning :type => :warning, + :warning_code => 63, + :fingerprint => "de0e11056b9f9af7b8570d5354185cd7e17a18cc61d627555fe4adfff00fb447", + :warning_type => "Cross Site Scripting", + :message => /^Rails\ 3\.2\.9\.rc2\ has\ an\ XSS\ vulnerability/, + :confidence => 1, + :relative_path => "Gemfile" + end end diff --git a/test/tests/rails32.rb b/test/tests/rails32.rb index 5b7d7f0db7d93e4987a633aa5c1aca2e6a071997..5534a43c66220cd773bb80880956a2fc40507231 100644 --- a/test/tests/rails32.rb +++ b/test/tests/rails32.rb @@ -88,6 +88,16 @@ class Rails32Tests < Test::Unit::TestCase :file => /Gemfile/ end + def test_i18n_xss_CVE_2013_4491 + assert_warning :type => :warning, + :warning_code => 63, + :fingerprint => "de0e11056b9f9af7b8570d5354185cd7e17a18cc61d627555fe4adfff00fb447", + :warning_type => "Cross Site Scripting", + :message => /^Rails\ 3\.2\.9\.rc2\ has\ an\ XSS\ vulnerability/, + :confidence => 1, + :relative_path => "Gemfile" + end + def test_redirect_1 assert_warning :type => :warning, :warning_type => "Redirect", diff --git a/test/tests/rails4.rb b/test/tests/rails4.rb index 70207d69d30bad82b983cc9e91f92f798bac1b4d..0a67783e49d1c990abfa5a44d6ad5db40c172c4f 100644 --- a/test/tests/rails4.rb +++ b/test/tests/rails4.rb @@ -143,4 +143,60 @@ class Rails4Tests < Test::Unit::TestCase :relative_path => "app/controllers/friendly_controller.rb", :user_input => s(:call, s(:params), :[], s(:lit, :query)) end + + def test_i18n_xss_CVE_2013_4491 + assert_warning :type => :warning, + :warning_code => 63, + :fingerprint => "de0e11056b9f9af7b8570d5354185cd7e17a18cc61d627555fe4adfff00fb447", + :warning_type => "Cross Site Scripting", + :message => /^Rails\ 4\.0\.0\ has\ an\ XSS\ vulnerability\ in\ /, + :confidence => 1, + :relative_path => "Gemfile" + end + + def test_denial_of_service_CVE_2013_6414 + assert_warning :type => :warning, + :warning_code => 64, + :fingerprint => "a7b00f08e4a18c09388ad017876e3f57d18040ead2816a2091f3301b6f0e5a00", + :warning_type => "Denial of Service", + :message => /^Rails\ 4\.0\.0\ has\ a\ denial\ of\ service\ vuln/, + :confidence => 1, + :relative_path => "Gemfile" + end + + def test_number_to_currency_CVE_2013_6415 + assert_warning :type => :template, + :warning_code => 66, + :fingerprint => "0fb96b5f4b3a4dcdc677d126f492441e2f7b46880563a977b1246b30d3c117a0", + :warning_type => "Cross Site Scripting", + :line => 9, + :message => /^Currency\ value\ in\ number_to_currency\ is\ /, + :confidence => 0, + :relative_path => "app/views/users/index.html.erb", + :user_input => s(:call, s(:call, nil, :params), :[], s(:lit, :currency)) + end + + def test_simple_format_xss_CVE_2013_6416 + assert_warning :type => :warning, + :warning_code => 67, + :fingerprint => "e950ee1043d7f66b7f6ce99c2bf0876bd3ce8cb12818b52565b905cdb6004bad", + :warning_type => "Cross Site Scripting", + :line => nil, + :message => /^Rails\ 4\.0\.0 has\ a\ vulnerability\ in/, + :confidence => 1, + :relative_path => "Gemfile", + :user_input => nil + end + + def test_sql_injection_CVE_2013_6417 + assert_warning :type => :warning, + :warning_code => 69, + :fingerprint => "e1b66f4311771d714a13be519693c540d7e917511a758827d9b2a0a7f958e40f", + :warning_type => "SQL Injection", + :line => nil, + :message => /^Rails\ 4\.0\.0 contains\ a\ SQL\ injection\ vul/, + :confidence => 0, + :relative_path => "Gemfile", + :user_input => nil + end end diff --git a/test/tests/rails4_with_engines.rb b/test/tests/rails4_with_engines.rb index 7ddb26787c04e026575af6e8ac3bf5243093a91e..efa9c07d48e8a9daed37f05a8b4ef8a0bce8d3ae 100644 --- a/test/tests/rails4_with_engines.rb +++ b/test/tests/rails4_with_engines.rb @@ -18,6 +18,16 @@ class Rails4WithEnginesTests < Test::Unit::TestCase Rails4WithEngines end + def test_i18n_xss_CVE_2013_4491 + assert_warning :type => :warning, + :warning_code => 63, + :fingerprint => "de0e11056b9f9af7b8570d5354185cd7e17a18cc61d627555fe4adfff00fb447", + :warning_type => "Cross Site Scripting", + :message => /^Rails\ 4\.0\.0\ has\ an\ XSS\ vulnerability\ in\ /, + :confidence => 1, + :relative_path => "Gemfile" + end + def test_redirect_1 assert_warning :type => :generic, :warning_code => 18,