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:
parent
2d5f323ca3
commit
188cb58daa
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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…"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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" => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue