Commit 3ed2de4c authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-337554-fix-markdown-regex-performance' into 'master'

Fix denial-of-service attack in Markdown parser

See merge request gitlab-org/security/gitlab!1696
parents eb9438c3 13157870
...@@ -26,14 +26,17 @@ module Banzai ...@@ -26,14 +26,17 @@ module Banzai
# Pattern to match a standard markdown link # Pattern to match a standard markdown link
# #
# Rubular: http://rubular.com/r/2EXEQ49rg5 # Rubular: http://rubular.com/r/2EXEQ49rg5
LINK_OR_IMAGE_PATTERN = %r{ #
(?<preview_operator>!)? # This pattern is vulnerable to malicious inputs, so use Gitlab::UntrustedRegexp
\[(?<text>.+?)\] # to place bounds on execution time
\( LINK_OR_IMAGE_PATTERN = Gitlab::UntrustedRegexp.new(
(?<new_link>.+?) '(?P<preview_operator>!)?' \
(?<title>\ ".+?")? '\[(?P<text>.+?)\]' \
\) '\(' \
}x.freeze '(?P<new_link>.+?)' \
'(?P<title>\ ".+?")?' \
'\)'
)
# Text matching LINK_OR_IMAGE_PATTERN inside these elements will not be linked # Text matching LINK_OR_IMAGE_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set IGNORE_PARENTS = %w(a code kbd pre script style).to_set
...@@ -48,7 +51,7 @@ module Banzai ...@@ -48,7 +51,7 @@ module Banzai
doc.xpath(TEXT_QUERY).each do |node| doc.xpath(TEXT_QUERY).each do |node|
content = node.to_html content = node.to_html
next unless content.match(LINK_OR_IMAGE_PATTERN) next unless LINK_OR_IMAGE_PATTERN.match(content)
html = spaced_link_filter(content) html = spaced_link_filter(content)
......
...@@ -2,18 +2,20 @@ ...@@ -2,18 +2,20 @@
module Gitlab module Gitlab
class StringRegexMarker < StringRangeMarker class StringRegexMarker < StringRangeMarker
# rubocop: disable CodeReuse/ActiveRecord
def mark(regex, group: 0, &block) def mark(regex, group: 0, &block)
ranges = [] ranges = []
offset = 0
raw_line.scan(regex) do while match = regex.match(raw_line[offset..])
begin_index, end_index = Regexp.last_match.offset(group) begin_index = match.begin(group) + offset
end_index = match.end(group) + offset
ranges << (begin_index..(end_index - 1)) ranges << (begin_index..(end_index - 1))
offset = end_index
end end
super(ranges, &block) super(ranges, &block)
end end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
...@@ -63,6 +63,16 @@ RSpec.describe Banzai::Filter::SpacedLinkFilter do ...@@ -63,6 +63,16 @@ RSpec.describe Banzai::Filter::SpacedLinkFilter do
end end
end end
it 'does not process malicious input' do
Timeout.timeout(10) do
doc = filter('[ (](' * 60_000)
found_links = doc.css('a')
expect(found_links.size).to eq(0)
end
end
it 'converts multiple URLs' do it 'converts multiple URLs' do
link1 = '[first](slug one)' link1 = '[first](slug one)'
link2 = '[second](http://example.com/slug two)' link2 = '[second](http://example.com/slug two)'
......
...@@ -23,9 +23,10 @@ RSpec.describe Gitlab::StringRegexMarker do ...@@ -23,9 +23,10 @@ RSpec.describe Gitlab::StringRegexMarker do
context 'with multiple occurrences' do context 'with multiple occurrences' do
let(:raw) { %{a <b> <c> d} } let(:raw) { %{a <b> <c> d} }
let(:rich) { %{a &lt;b&gt; &lt;c&gt; d}.html_safe } let(:rich) { %{a &lt;b&gt; &lt;c&gt; d}.html_safe }
let(:regexp) { /<[a-z]>/ }
subject do subject do
described_class.new(raw, rich).mark(/<[a-z]>/) do |text, left:, right:, mode:| described_class.new(raw, rich).mark(regexp) do |text, left:, right:, mode:|
%{<strong>#{text}</strong>}.html_safe %{<strong>#{text}</strong>}.html_safe
end end
end end
...@@ -34,6 +35,15 @@ RSpec.describe Gitlab::StringRegexMarker do ...@@ -34,6 +35,15 @@ RSpec.describe Gitlab::StringRegexMarker do
expect(subject).to eq(%{a <strong>&lt;b&gt;</strong> <strong>&lt;c&gt;</strong> d}) expect(subject).to eq(%{a <strong>&lt;b&gt;</strong> <strong>&lt;c&gt;</strong> d})
expect(subject).to be_html_safe expect(subject).to be_html_safe
end end
context 'with a Gitlab::UntrustedRegexp' do
let(:regexp) { Gitlab::UntrustedRegexp.new('<[a-z]>') }
it 'marks the matches' do
expect(subject).to eq(%{a <strong>&lt;b&gt;</strong> <strong>&lt;c&gt;</strong> d})
expect(subject).to be_html_safe
end
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment