From 7328a2bfb056915efc1e42dd1eea64da28b11f12 Mon Sep 17 00:00:00 2001 From: Bianca Nenciu Date: Wed, 25 May 2022 14:51:47 +0300 Subject: [PATCH] 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. --- app/services/word_watcher.rb | 33 +++++++++++++++++++++----------- lib/inline_oneboxer.rb | 1 + spec/lib/inline_oneboxer_spec.rb | 18 +++++++++++++++++ 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/services/word_watcher.rb b/app/services/word_watcher.rb index 28f38b9a97e..bc056fd6b46 100644 --- a/app/services/word_watcher.rb +++ b/app/services/word_watcher.rb @@ -103,21 +103,18 @@ class WordWatcher doc = Nokogiri::HTML5::fragment(html) doc.traverse do |node| - 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 + node.content = censor_text_with_regexp(node.content, regexp) if node.text? end doc.to_s 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! WatchedWord.actions.each do |a, i| Discourse.cache.delete word_matcher_regexp_key(a) @@ -172,4 +169,18 @@ class WordWatcher def word_matches?(word) Regexp.new(WordWatcher.word_to_regexp(word, whole: true), Regexp::IGNORECASE).match?(@raw) 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 diff --git a/lib/inline_oneboxer.rb b/lib/inline_oneboxer.rb index 22bf4762378..7b4d78fc7ab 100644 --- a/lib/inline_oneboxer.rb +++ b/lib/inline_oneboxer.rb @@ -108,6 +108,7 @@ class InlineOneboxer end end 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] onebox end diff --git a/spec/lib/inline_oneboxer_spec.rb b/spec/lib/inline_oneboxer_spec.rb index 10dcb4b607b..0590d5b1303 100644 --- a/spec/lib/inline_oneboxer_spec.rb +++ b/spec/lib/inline_oneboxer_spec.rb @@ -313,6 +313,24 @@ describe InlineOneboxer do expect(onebox[:title]).to be_blank 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: "welcome to my blog") + + 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 context "register_local_handler" do