SECURITY: Fixes for main (#28137)

* SECURITY: Update default allowed iframes list

Change the default iframe url list to all include 3 slashes.

* SECURITY: limit group tag's name length

Limit the size of a group tag's name to 100 characters.

Internal ref - t/130059

* SECURITY: Improve sanitization of SVGs in Onebox

---------

Co-authored-by: Blake Erickson <o.blakeerickson@gmail.com>
Co-authored-by: Régis Hanol <regis@hanol.fr>
Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
Natalie Tay 2024-07-30 14:19:01 +08:00 committed by GitHub
parent 2d5f323ca3
commit 188cb58daa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 75 additions and 8 deletions

View File

@ -65,8 +65,10 @@ export default function buildOptions(state) {
? siteSettings.allowed_href_schemes.split("|") ? siteSettings.allowed_href_schemes.split("|")
: null, : null,
allowedIframes: siteSettings.allowed_iframes allowedIframes: siteSettings.allowed_iframes
? siteSettings.allowed_iframes.split("|") ? siteSettings.allowed_iframes
: [], .split("|")
.filter((str) => (str.match(/\//g) || []).length >= 3)
: [], // Only allow urls with at least 3 slashes. Ex: 'https://example.com/'.
markdownIt: true, markdownIt: true,
previewing, previewing,
disableEmojis, disableEmojis,

View File

@ -70,6 +70,18 @@ module("Unit | Utility | pretty-text", function (hooks) {
.features.emoji, .features.emoji,
"emoji disabled" "emoji disabled"
); );
assert.deepEqual(
build({ siteSettings: { allowed_iframes: "https://example.com/" } })
.options.discourse.allowedIframes,
["https://example.com/"],
"it doesn't filter out valid urls"
);
assert.deepEqual(
build({ siteSettings: { allowed_iframes: "https://example.com" } })
.options.discourse.allowedIframes,
[],
"it filters out invalid urls. Requires 3 slashes."
);
}); });
test("basic cooking", function (assert) { test("basic cooking", function (assert) {

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class TagGroup < ActiveRecord::Base class TagGroup < ActiveRecord::Base
validates :name, length: { maximum: 100 }
validates_uniqueness_of :name, case_sensitive: false validates_uniqueness_of :name, case_sensitive: false
has_many :tag_group_memberships, dependent: :destroy has_many :tag_group_memberships, dependent: :destroy
@ -118,7 +119,7 @@ end
# Table name: tag_groups # Table name: tag_groups
# #
# id :integer not null, primary key # id :integer not null, primary key
# name :string not null # name :string(100) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# parent_tag_id :integer # parent_tag_id :integer

View File

@ -4610,7 +4610,7 @@ en:
everyone_can_use: "Tags can be used by everyone" everyone_can_use: "Tags can be used by everyone"
usable_only_by_groups: "Tags are visible to everyone, but only the following groups can use them" usable_only_by_groups: "Tags are visible to everyone, but only the following groups can use them"
visible_only_to_groups: "Tags are visible only to the following groups" visible_only_to_groups: "Tags are visible only to the following groups"
cannot_save: "Cannot save tag group. Make sure that there is at least one tag present, tag group name is not empty, and a group is selected for tags permission." cannot_save: "Cannot save tag group. Make sure that there is at least one tag present, tag group name is not empty and less than 100 characters, and a group is selected for tags permission."
tags_placeholder: "Search or create tags" tags_placeholder: "Search or create tags"
parent_tag_placeholder: "Optional" parent_tag_placeholder: "Optional"
select_groups_placeholder: "Select groups…" select_groups_placeholder: "Select groups…"

View File

@ -2016,7 +2016,7 @@ security:
allow_any: false allow_any: false
choices: "['*'] + Onebox::Engine.all_iframe_origins" choices: "['*'] + Onebox::Engine.all_iframe_origins"
allowed_iframes: allowed_iframes:
default: "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|https://open.spotify.com" default: "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com/|https://open.spotify.com/"
type: list type: list
list_type: simple list_type: simple
client: true client: true

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class LimitTagGroupNameLength < ActiveRecord::Migration[7.0]
def change
DB.exec <<~SQL
UPDATE tag_groups
SET name = LEFT(name, 100)
WHERE LENGTH(name) > 100
SQL
change_column :tag_groups, :name, :string, limit: 100
end
end

View File

@ -79,6 +79,13 @@ module Onebox
iframe.remove_attribute("src") iframe.remove_attribute("src")
end end
end, end,
lambda do |env|
next if env[:node_name] != "svg"
env[:node].traverse do |node|
next if node.element? && %w[svg path use].include?(node.name)
node.remove
end
end,
], ],
protocols: { protocols: {
"embed" => { "embed" => {

View File

@ -118,4 +118,36 @@ RSpec.describe Onebox::Preview do
expect(result).to include ' src="https://thirdparty.example.com"' expect(result).to include ' src="https://thirdparty.example.com"'
end end
end end
describe "svg sanitization" do
it "does not allow unexpected elements inside svg" do
preview = described_class.new(preview_url)
preview.stubs(:engine_html).returns <<~HTML.strip
<svg><style>/*Text*/</style></svg>
HTML
result = preview.to_s
expect(result).to eq("<svg></svg>")
end
it "does not allow text inside svg" do
preview = described_class.new(preview_url)
preview.stubs(:engine_html).returns <<~HTML.strip
<svg>Hello world</svg>
HTML
result = preview.to_s
expect(result).to eq("<svg></svg>")
end
it "allows simple svg" do
simple_svg =
'<svg height="210" width="400"><path d="M150 5 L75 200 L225 200 Z" style="fill:none;stroke:green;stroke-width:3"></path></svg>'
preview = described_class.new(preview_url)
preview.stubs(:engine_html).returns simple_svg
result = preview.to_s
expect(result).to eq(simple_svg)
end
end
end end

View File

@ -2529,17 +2529,17 @@ HTML
end end
it "can properly allowlist iframes" do it "can properly allowlist iframes" do
SiteSetting.allowed_iframes = "https://bob.com/a|http://silly.com?EMBED=" SiteSetting.allowed_iframes = "https://bob.com/a|http://silly.com/?EMBED="
raw = <<~HTML raw = <<~HTML
<iframe src='https://www.google.com/maps/Embed?testing'></iframe> <iframe src='https://www.google.com/maps/Embed?testing'></iframe>
<iframe src='https://bob.com/a?testing'></iframe> <iframe src='https://bob.com/a?testing'></iframe>
<iframe src='HTTP://SILLY.COM?EMBED=111'></iframe> <iframe src='HTTP://SILLY.COM/?EMBED=111'></iframe>
HTML HTML
# we require explicit HTTPS here # we require explicit HTTPS here
html = <<~HTML html = <<~HTML
<iframe src="https://bob.com/a?testing"></iframe> <iframe src="https://bob.com/a?testing"></iframe>
<iframe src="HTTP://SILLY.COM?EMBED=111"></iframe> <iframe src="HTTP://SILLY.COM/?EMBED=111"></iframe>
HTML HTML
cooked = PrettyText.cook(raw).strip cooked = PrettyText.cook(raw).strip