Project

General

Profile

Feature #32424 » 0007-Relax-allowed-protocols-in-links-by-denying-specific.patch

Marius BĂLTEANU, 2021-07-04 12:21

View differences:

lib/redmine/helpers/url.rb
22 22
module Redmine
23 23
  module Helpers
24 24
    module URL
25
      # safe for resources fetched without user interaction?
25 26
      def uri_with_safe_scheme?(uri, schemes = ['http', 'https', 'ftp', 'mailto', nil])
26 27
        # URLs relative to the current document or document root (without a protocol
27 28
        # separator, should be harmless
......
32 33
      rescue URI::Error
33 34
        false
34 35
      end
36

  
37
      # safe to render links to given uri?
38
      def uri_with_link_safe_scheme?(uri)
39
        # regexp adapted from Sanitize (we need to catch even invalid protocol specs)
40
        return true unless uri =~ /\A\s*([^\/#]*?)(?:\:|&#0*58|&#x0*3a)/i
41

  
42
        # absolute scheme
43
        scheme = $1.downcase
44
        return false unless /\A[a-z][a-z0-9\+\.\-]*\z/.match?(scheme) # RFC 3986
45

  
46
        %w(data javascript vbscript).none?(scheme)
47
      end
35 48
    end
36 49
  end
37 50
end
lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb
22 22
    module CommonMark
23 23
      # sanitizes rendered HTML using the Sanitize gem
24 24
      class SanitizationFilter < HTML::Pipeline::SanitizationFilter
25
        include Redmine::Helpers::URL
26
        RELAXED_PROTOCOL_ATTRS = {
27
          "a" => %w(href).freeze,
28
        }.freeze
29

  
25 30
        def whitelist
26 31
          @@whitelist ||= customize_whitelist(super.deep_dup)
27 32
        end
......
72 77
            node.remove_attribute("id")
73 78
          }
74 79

  
75
          # allow the same set of URL schemes for links as is the default in
76
          # Redmine::Helpers::URL#uri_with_safe_scheme?
77
          whitelist[:protocols]["a"]["href"] = [
78
            'http', 'https', 'ftp', 'mailto', :relative
79
          ]
80
          # https://github.com/rgrove/sanitize/issues/209
81
          whitelist[:protocols].delete("a")
82
          whitelist[:transformers].push lambda{|env|
83
            node = env[:node]
84
            return if node.type != Nokogiri::XML::Node::ELEMENT_NODE
85

  
86
            name = env[:node_name]
87
            return unless RELAXED_PROTOCOL_ATTRS.include?(name)
88

  
89
            RELAXED_PROTOCOL_ATTRS[name].each do |attr|
90
              next unless node.has_attribute?(attr)
91

  
92
              node[attr] = node[attr].strip
93
              unless !node[attr].empty? && uri_with_link_safe_scheme?(node[attr])
94
                node.remove_attribute(attr)
95
              end
96
            end
97
          }
80 98

  
81 99
          whitelist
82 100
        end
test/unit/lib/redmine/helpers/url_test.rb
33 33
    assert_not uri_with_safe_scheme?("httpx://example.com/")
34 34
    assert_not uri_with_safe_scheme?("mailto:root@")
35 35
  end
36

  
37
  LINK_SAFE_URIS = [
38
    "http://example.com/",
39
    "https://example.com/",
40
    "ftp://example.com/",
41
    "foo://example.org",
42
    "mailto:foo@example.org",
43
    " http://example.com/",
44
    "",
45
    "/javascript:alert(\'filename\')",
46
  ]
47

  
48
  def test_uri_with_link_safe_scheme_should_recognize_safe_uris
49
    LINK_SAFE_URIS.each do |uri|
50
      assert uri_with_link_safe_scheme?(uri), "'#{uri}' should be safe"
51
    end
52
  end
53

  
54
  LINK_UNSAFE_URIS = [
55
    "javascript:alert(\'XSS\');",
56
    "javascript    :alert(\'XSS\');",
57
    "javascript:    alert(\'XSS\');",
58
    "javascript    :   alert(\'XSS\');",
59
    ":javascript:alert(\'XSS\');",
60
    "javascript&#58;",
61
    "javascript&#0058;",
62
    "javascript&#x3A;",
63
    "javascript&#x003A;",
64
    "java\0script:alert(\"XSS\")",
65
    "java\script:alert(\"XSS\")",
66
    " \x0e  javascript:alert(\'XSS\');",
67
    "data:image/png;base64,foobar",
68
    "vbscript:foobar",
69
    "data:text/html;base64,foobar",
70
  ]
71

  
72
  def test_uri_with_link_safe_scheme_should_recognize_unsafe_uris
73
    LINK_UNSAFE_URIS.each do |uri|
74
      assert_not uri_with_link_safe_scheme?(uri), "'#{uri}' should not be safe"
75
    end
76
  end
36 77
end
test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb
71 71
      assert_equal %(<code>foo</code>), filter(input)
72 72
    end
73 73

  
74
    def test_should_allow_links_with_safe_url_schemes
75
      %w(http https ftp ssh foo).each do |scheme|
76
        input = %(<a href="#{scheme}://example.org/">foo</a>)
77
        assert_equal input, filter(input)
78
      end
79
    end
80

  
81
    def test_should_allow_mailto_links
82
      input = %(<a href="mailto:foo@example.org">bar</a>)
83
      assert_equal input, filter(input)
84
    end
85

  
86
    def test_should_remove_empty_link
87
      input = %(<a href="">bar</a>)
88
      assert_equal %(<a>bar</a>), filter(input)
89
      input = %(<a href=" ">bar</a>)
90
      assert_equal %(<a>bar</a>), filter(input)
91
    end
92

  
74 93
    # samples taken from the Sanitize test suite
75 94
    # rubocop:disable Layout/LineLength
76 95
    STRINGS = [
......
194 213
        '<a href="vbscript:foobar">XSS</a>',
195 214
        '<a>XSS</a>'
196 215
      ],
197

  
198
      'invalid URIs' => [
199
        '<a href="foo://example.org">link</a>',
200
        '<a>link</a>'
201
      ],
202 216
    }
203 217

  
204 218
    PROTOCOLS.each do |name, strings|
(16-16/26)