1
0
mirror of https://github.com/meineerde/redmine.git synced 2026-01-31 19:47:14 +00:00

Relax allowed protocols in links by denying specific protocols for CommonMark text formatting (#32424).

Patch by Martin Cizek.

git-svn-id: http://svn.redmine.org/redmine/trunk@21161 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Marius Balteanu 2021-08-11 21:49:27 +00:00
parent 46ecdcec4d
commit e8c911577f
4 changed files with 96 additions and 10 deletions

View File

@ -22,6 +22,7 @@ require 'uri'
module Redmine
module Helpers
module URL
# safe for resources fetched without user interaction?
def uri_with_safe_scheme?(uri, schemes = ['http', 'https', 'ftp', 'mailto', nil])
# URLs relative to the current document or document root (without a protocol
# separator, should be harmless
@ -32,6 +33,18 @@ module Redmine
rescue URI::Error
false
end
# safe to render links to given uri?
def uri_with_link_safe_scheme?(uri)
# regexp adapted from Sanitize (we need to catch even invalid protocol specs)
return true unless uri =~ /\A\s*([^\/#]*?)(?:\:|&#0*58|&#x0*3a)/i
# absolute scheme
scheme = $1.downcase
return false unless /\A[a-z][a-z0-9\+\.\-]*\z/.match?(scheme) # RFC 3986
%w(data javascript vbscript).none?(scheme)
end
end
end
end

View File

@ -22,6 +22,11 @@ module Redmine
module CommonMark
# sanitizes rendered HTML using the Sanitize gem
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
include Redmine::Helpers::URL
RELAXED_PROTOCOL_ATTRS = {
"a" => %w(href).freeze,
}.freeze
def whitelist
@@whitelist ||= customize_whitelist(super.deep_dup)
end
@ -72,11 +77,24 @@ module Redmine
node.remove_attribute("id")
}
# allow the same set of URL schemes for links as is the default in
# Redmine::Helpers::URL#uri_with_safe_scheme?
whitelist[:protocols]["a"]["href"] = [
'http', 'https', 'ftp', 'mailto', :relative
]
# https://github.com/rgrove/sanitize/issues/209
whitelist[:protocols].delete("a")
whitelist[:transformers].push lambda{|env|
node = env[:node]
return if node.type != Nokogiri::XML::Node::ELEMENT_NODE
name = env[:node_name]
return unless RELAXED_PROTOCOL_ATTRS.include?(name)
RELAXED_PROTOCOL_ATTRS[name].each do |attr|
next unless node.has_attribute?(attr)
node[attr] = node[attr].strip
unless !node[attr].empty? && uri_with_link_safe_scheme?(node[attr])
node.remove_attribute(attr)
end
end
}
whitelist
end

View File

@ -33,4 +33,45 @@ class URLTest < ActiveSupport::TestCase
assert_not uri_with_safe_scheme?("httpx://example.com/")
assert_not uri_with_safe_scheme?("mailto:root@")
end
LINK_SAFE_URIS = [
"http://example.com/",
"https://example.com/",
"ftp://example.com/",
"foo://example.org",
"mailto:foo@example.org",
" http://example.com/",
"",
"/javascript:alert(\'filename\')",
]
def test_uri_with_link_safe_scheme_should_recognize_safe_uris
LINK_SAFE_URIS.each do |uri|
assert uri_with_link_safe_scheme?(uri), "'#{uri}' should be safe"
end
end
LINK_UNSAFE_URIS = [
"javascript:alert(\'XSS\');",
"javascript :alert(\'XSS\');",
"javascript: alert(\'XSS\');",
"javascript : alert(\'XSS\');",
":javascript:alert(\'XSS\');",
"javascript&#58;",
"javascript&#0058;",
"javascript&#x3A;",
"javascript&#x003A;",
"java\0script:alert(\"XSS\")",
"java\script:alert(\"XSS\")",
" \x0e javascript:alert(\'XSS\');",
"data:image/png;base64,foobar",
"vbscript:foobar",
"data:text/html;base64,foobar",
]
def test_uri_with_link_safe_scheme_should_recognize_unsafe_uris
LINK_UNSAFE_URIS.each do |uri|
assert_not uri_with_link_safe_scheme?(uri), "'#{uri}' should not be safe"
end
end
end

View File

@ -71,6 +71,25 @@ if Object.const_defined?(:CommonMarker)
assert_equal %(<code>foo</code>), filter(input)
end
def test_should_allow_links_with_safe_url_schemes
%w(http https ftp ssh foo).each do |scheme|
input = %(<a href="#{scheme}://example.org/">foo</a>)
assert_equal input, filter(input)
end
end
def test_should_allow_mailto_links
input = %(<a href="mailto:foo@example.org">bar</a>)
assert_equal input, filter(input)
end
def test_should_remove_empty_link
input = %(<a href="">bar</a>)
assert_equal %(<a>bar</a>), filter(input)
input = %(<a href=" ">bar</a>)
assert_equal %(<a>bar</a>), filter(input)
end
# samples taken from the Sanitize test suite
# rubocop:disable Layout/LineLength
STRINGS = [
@ -194,11 +213,6 @@ if Object.const_defined?(:CommonMarker)
'<a href="vbscript:foobar">XSS</a>',
'<a>XSS</a>'
],
'invalid URIs' => [
'<a href="foo://example.org">link</a>',
'<a>link</a>'
],
}
PROTOCOLS.each do |name, strings|