FIX: Apply censored words to inline onebox (#16873)

Censored watched words were not censored inside the title of an inline
oneboxes. Malicious users could exploit this behaviour to insert bad
words. The same issue has been fixed for regular Oneboxes in commit
d184fe59ca7885741ed9f840d3209a9a5ed861ea.
This commit is contained in:
Bianca Nenciu
2022-05-25 14:51:47 +03:00
committed by GitHub
parent 6268fe7495
commit 7328a2bfb0
3 changed files with 41 additions and 11 deletions

View File

@ -103,21 +103,18 @@ class WordWatcher
doc = Nokogiri::HTML5::fragment(html) doc = Nokogiri::HTML5::fragment(html)
doc.traverse do |node| doc.traverse do |node|
if node.text? node.content = censor_text_with_regexp(node.content, regexp) if node.text?
node.content = node.content.gsub(regexp) do |match|
# the regex captures leading whitespaces
padding = match.size - match.lstrip.size
if padding > 0
match[0..padding - 1] + REPLACEMENT_LETTER * (match.size - padding)
else
REPLACEMENT_LETTER * match.size
end
end
end
end end
doc.to_s doc.to_s
end end
def self.censor_text(text)
regexp = WordWatcher.word_matcher_regexp(:censor)
return text if regexp.blank?
censor_text_with_regexp(text, regexp)
end
def self.clear_cache! def self.clear_cache!
WatchedWord.actions.each do |a, i| WatchedWord.actions.each do |a, i|
Discourse.cache.delete word_matcher_regexp_key(a) Discourse.cache.delete word_matcher_regexp_key(a)
@ -172,4 +169,18 @@ class WordWatcher
def word_matches?(word) def word_matches?(word)
Regexp.new(WordWatcher.word_to_regexp(word, whole: true), Regexp::IGNORECASE).match?(@raw) Regexp.new(WordWatcher.word_to_regexp(word, whole: true), Regexp::IGNORECASE).match?(@raw)
end end
private
def self.censor_text_with_regexp(text, regexp)
text.gsub(regexp) do |match|
# the regex captures leading whitespaces
padding = match.size - match.lstrip.size
if padding > 0
match[0..padding - 1] + REPLACEMENT_LETTER * (match.size - padding)
else
REPLACEMENT_LETTER * match.size
end
end
end
end end

View File

@ -108,6 +108,7 @@ class InlineOneboxer
end end
end end
onebox = { url: url, title: title && Emoji.gsub_emoji_to_unicode(title) } onebox = { url: url, title: title && Emoji.gsub_emoji_to_unicode(title) }
onebox[:title] = WordWatcher.censor_text(onebox[:title])
Discourse.cache.write(cache_key(url), onebox, expires_in: 1.day) if !opts[:skip_cache] Discourse.cache.write(cache_key(url), onebox, expires_in: 1.day) if !opts[:skip_cache]
onebox onebox
end end

View File

@ -313,6 +313,24 @@ describe InlineOneboxer do
expect(onebox[:title]).to be_blank expect(onebox[:title]).to be_blank
end end
end end
it "censors external oneboxes" do
Fabricate(:watched_word, action: WatchedWord.actions[:censor], word: "my")
SiteSetting.enable_inline_onebox_on_all_domains = true
stub_request(:get, "https://eviltrout.com/some-path").
to_return(status: 200, body: "<html><head><title>welcome to my blog</title></head></html>")
onebox = InlineOneboxer.lookup(
"https://eviltrout.com/some-path",
skip_cache: true
)
expect(onebox).to be_present
expect(onebox[:url]).to eq("https://eviltrout.com/some-path")
expect(onebox[:title]).to eq("welcome to ■■ blog")
end
end end
context "register_local_handler" do context "register_local_handler" do