diff options
author | Robert Speicher <robert@gitlab.com> | 2016-06-13 03:25:01 +0000 |
---|---|---|
committer | Robert Speicher <robert@gitlab.com> | 2016-06-13 03:25:01 +0000 |
commit | 06a99cf7ea82105a182c2805ea05266e5ce6c0a2 (patch) | |
tree | 255e995c6205562ea561266e6849823654eed8c1 | |
parent | 8d243f9bdacea1909bf503eb715bd437c3b48aa7 (diff) | |
parent | 7c87dac5f1f3d333b06dbcb2c7f66538abfb8255 (diff) | |
download | gitlab-ce-06a99cf7ea82105a182c2805ea05266e5ce6c0a2.tar.gz |
Merge branch 'bug/svg_sanitizer' into 'master'
Improve SVG sanitizer to handle namespaced attributes
* Small refactor in the SVG sanitizer
* Enable already whitelisted namespaced attributes to be allowed
* Disable `xlink:href`to reference any external resource
Fixes #18100
See merge request !4427
-rw-r--r-- | CHANGELOG | 1 | ||||
-rw-r--r-- | lib/gitlab/sanitizers/svg.rb | 48 | ||||
-rw-r--r-- | spec/lib/gitlab/sanitizers/svg_spec.rb | 94 |
3 files changed, 130 insertions, 13 deletions
diff --git a/CHANGELOG b/CHANGELOG index 99dd1d3ff4a..364690286e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.9.0 (unreleased) - Fix issue todo not remove when leave project !4150 (Long Nguyen) - Allow customisable text on the 'nearly there' page after a user signs up - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support + - Fix SVG sanitizer to allow more elements - Allow forking projects with restricted visibility level - Added descriptions to notification settings dropdown - Improve note validation to prevent errors when creating invalid note via API diff --git a/lib/gitlab/sanitizers/svg.rb b/lib/gitlab/sanitizers/svg.rb index 5e95f6c0529..8304b9a482c 100644 --- a/lib/gitlab/sanitizers/svg.rb +++ b/lib/gitlab/sanitizers/svg.rb @@ -12,23 +12,45 @@ module Gitlab def scrub(node) unless Whitelist::ALLOWED_ELEMENTS.include?(node.name) node.unlink - else - node.attributes.each do |attr_name, attr| - valid_attributes = Whitelist::ALLOWED_ATTRIBUTES[node.name] - - unless valid_attributes && valid_attributes.include?(attr_name) - if Whitelist::ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name) && - attr_name.start_with?('data-') - # Arbitrary data attributes are allowed. Verify that the attribute - # is a valid data attribute. - attr.unlink unless attr_name =~ DATA_ATTR_PATTERN - else - attr.unlink - end + return + end + + valid_attributes = Whitelist::ALLOWED_ATTRIBUTES[node.name] + return unless valid_attributes + + node.attribute_nodes.each do |attr| + attr_name = attribute_name_with_namespace(attr) + + if valid_attributes.include?(attr_name) + attr.unlink if unsafe_href?(attr) + else + # Arbitrary data attributes are allowed. + unless allows_data_attribute?(node) && data_attribute?(attr) + attr.unlink end end end end + + def attribute_name_with_namespace(attr) + if attr.namespace + "#{attr.namespace.prefix}:#{attr.name}" + else + attr.name + end + end + + def allows_data_attribute?(node) + Whitelist::ALLOWED_DATA_ATTRIBUTES_IN_ELEMENTS.include?(node.name) + end + + def unsafe_href?(attr) + attribute_name_with_namespace(attr) == 'xlink:href' && !attr.value.start_with?('#') + end + + def data_attribute?(attr) + attr.name.start_with?('data-') && attr.name =~ DATA_ATTR_PATTERN && attr.namespace.nil? + end end end end diff --git a/spec/lib/gitlab/sanitizers/svg_spec.rb b/spec/lib/gitlab/sanitizers/svg_spec.rb new file mode 100644 index 00000000000..030c2063ab2 --- /dev/null +++ b/spec/lib/gitlab/sanitizers/svg_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' + +describe Gitlab::Sanitizers::SVG do + let(:scrubber) { Gitlab::Sanitizers::SVG::Scrubber.new } + let(:namespace) { double(Nokogiri::XML::Namespace, prefix: 'xlink', href: 'http://www.w3.org/1999/xlink') } + let(:namespaced_attr) { double(Nokogiri::XML::Attr, name: 'href', namespace: namespace, value: '#awesome_id') } + + describe '.clean' do + let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') } + let(:data) { open(input_svg_path).read } + let(:sanitized_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'sanitized.svg') } + let(:sanitized) { open(sanitized_svg_path).read } + + it 'delegates sanitization to scrubber' do + expect_any_instance_of(Gitlab::Sanitizers::SVG::Scrubber).to receive(:scrub).at_least(:once) + described_class.clean(data) + end + + it 'returns sanitized data' do + expect(described_class.clean(data)).to eq(sanitized) + end + end + + context 'scrubber' do + describe '#scrub' do + let(:invalid_element) { double(Nokogiri::XML::Node, name: 'invalid', value: 'invalid') } + let(:invalid_attribute) { double(Nokogiri::XML::Attr, name: 'invalid', namespace: nil) } + let(:valid_element) { double(Nokogiri::XML::Node, name: 'use') } + + it 'removes an invalid element' do + expect(invalid_element).to receive(:unlink) + + scrubber.scrub(invalid_element) + end + + it 'removes an invalid attribute' do + allow(valid_element).to receive(:attribute_nodes) { [invalid_attribute] } + expect(invalid_attribute).to receive(:unlink) + + scrubber.scrub(valid_element) + end + + it 'accepts valid element' do + allow(valid_element).to receive(:attribute_nodes) { [namespaced_attr] } + expect(valid_element).not_to receive(:unlink) + + scrubber.scrub(valid_element) + end + + it 'accepts valid namespaced attributes' do + allow(valid_element).to receive(:attribute_nodes) { [namespaced_attr] } + expect(namespaced_attr).not_to receive(:unlink) + + scrubber.scrub(valid_element) + end + end + + describe '#attribute_name_with_namespace' do + it 'returns name with prefix when attribute is namespaced' do + expect(scrubber.attribute_name_with_namespace(namespaced_attr)).to eq('xlink:href') + end + end + + describe '#unsafe_href?' do + let(:unsafe_attr) { double(Nokogiri::XML::Attr, name: 'href', namespace: namespace, value: 'http://evilsite.example.com/random.svg') } + + it 'returns true if href attribute is an external url' do + expect(scrubber.unsafe_href?(unsafe_attr)).to be_truthy + end + + it 'returns false if href atttribute is an internal reference' do + expect(scrubber.unsafe_href?(namespaced_attr)).to be_falsey + end + end + + describe '#data_attribute?' do + let(:data_attr) { double(Nokogiri::XML::Attr, name: 'data-gitlab', namespace: nil, value: 'gitlab is awesome') } + let(:namespaced_attr) { double(Nokogiri::XML::Attr, name: 'data-gitlab', namespace: namespace, value: 'gitlab is awesome') } + let(:other_attr) { double(Nokogiri::XML::Attr, name: 'something', namespace: nil, value: 'content') } + + it 'returns true if is a valid data attribute' do + expect(scrubber.data_attribute?(data_attr)).to be_truthy + end + + it 'returns false if attribute is namespaced' do + expect(scrubber.data_attribute?(namespaced_attr)).to be_falsey + end + + it 'returns false if not a data attribute' do + expect(scrubber.data_attribute?(other_attr)).to be_falsey + end + end + end +end |