SECURITY: fix XSS in excerpt parser

This commit is contained in:
Sam 2014-07-25 12:15:43 +10:00
parent fbbd4999b6
commit 6e9f5f5584
3 changed files with 37 additions and 24 deletions

View File

@ -23,8 +23,15 @@ class ExcerptParser < Nokogiri::XML::SAX::Document
me.excerpt me.excerpt
end end
def escape_attribute(v)
v.gsub("&", "&amp;")
.gsub("\"", "&#34;")
.gsub("<", "&lt;")
.gsub(">", "&gt;")
end
def include_tag(name, attributes) def include_tag(name, attributes)
characters("<#{name} #{attributes.map{|k,v| "#{k}='#{v}'"}.join(' ')}>", false, false, false) characters("<#{name} #{attributes.map{|k,v| "#{k}=\"#{escape_attribute(v)}\""}.join(' ')}>", false, false, false)
end end
def start_element(name, attributes=[]) def start_element(name, attributes=[])

View File

@ -75,6 +75,15 @@ describe PrettyText do
describe "Excerpt" do describe "Excerpt" do
it "sanitizes attempts to inject invalid attributes" do
spinner = "<a href=\"http://thedailywtf.com/\" data-bbcode=\"' class='fa fa-spin\">WTF</a>"
PrettyText.excerpt(spinner, 20).should match_html spinner
spinner = %q{<a href="http://thedailywtf.com/" title="' class=&quot;fa fa-spin&quot;&gt;&lt;img src='http://thedailywtf.com/Resources/Images/Primary/logo.gif"></a>}
PrettyText.excerpt(spinner, 20).should match_html spinner
end
context "images" do context "images" do
it "should dump images" do it "should dump images" do
@ -94,8 +103,8 @@ describe PrettyText do
end end
it "should keep spoilers" do it "should keep spoilers" do
PrettyText.excerpt("<div class='spoiler'><img src='http://cnn.com/a.gif'></div>", 100).should == "<span class='spoiler'>[image]</span>" PrettyText.excerpt("<div class='spoiler'><img src='http://cnn.com/a.gif'></div>", 100).should match_html "<span class='spoiler'>[image]</span>"
PrettyText.excerpt("<span class='spoiler'>spoiler</div>", 100).should == "<span class='spoiler'>spoiler</span>" PrettyText.excerpt("<span class='spoiler'>spoiler</div>", 100).should match_html "<span class='spoiler'>spoiler</span>"
end end
end end
@ -104,7 +113,7 @@ describe PrettyText do
end end
it "should preserve links" do it "should preserve links" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",100).should == "<a href='http://cnn.com'>cnn</a>" PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",100).should match_html "<a href='http://cnn.com'>cnn</a>"
end end
it "should deal with special keys properly" do it "should deal with special keys properly" do
@ -125,15 +134,15 @@ describe PrettyText do
end end
it "should not count the surrounds of a link" do it "should not count the surrounds of a link" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",3).should == "<a href='http://cnn.com'>cnn</a>" PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",3).should match_html "<a href='http://cnn.com'>cnn</a>"
end end
it "uses an ellipsis instead of html entities if provided with the option" do it "uses an ellipsis instead of html entities if provided with the option" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>", 2, text_entities: true).should == "<a href='http://cnn.com'>cn...</a>" PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>", 2, text_entities: true).should match_html "<a href='http://cnn.com'>cn...</a>"
end end
it "should truncate links" do it "should truncate links" do
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",2).should == "<a href='http://cnn.com'>cn&hellip;</a>" PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",2).should match_html "<a href='http://cnn.com'>cn&hellip;</a>"
end end
it "doesn't extract empty quotes as links" do it "doesn't extract empty quotes as links" do
@ -294,9 +303,6 @@ describe PrettyText do
PrettyText.cook("**你hello**").should match_html "<p><strong>你hello</strong></p>" PrettyText.cook("**你hello**").should match_html "<p><strong>你hello</strong></p>"
end end
it "sanitizes attempts to inject invalid attributes" do
PrettyText.cook("<a href=\"http://thedailywtf.com/\" data-bbcode=\"' class='fa fa-spin\">WTF</a>").should == "<p><a href=\"http://thedailywtf.com/\" rel=\"nofollow\">WTF</a></p>"
end
end end
end end

View File

@ -57,7 +57,7 @@ describe UserProfile do
end end
context 'with a user that has a link in their bio' do context 'with a user that has a link in their bio' do
let(:user_profile) { Fabricate.build(:user_profile, bio_raw: "im sissy and i love http://ponycorns.com") } let(:user_profile) { Fabricate.build(:user_profile, bio_raw: "I love http://discourse.org") }
let(:user) do let(:user) do
user = Fabricate.build(:user, user_profile: user_profile) user = Fabricate.build(:user, user_profile: user_profile)
user_profile.user = user user_profile.user = user
@ -66,22 +66,22 @@ describe UserProfile do
let(:created_user) do let(:created_user) do
user = Fabricate(:user) user = Fabricate(:user)
user.user_profile.bio_raw = 'im sissy and i love http://ponycorns.com' user.user_profile.bio_raw = 'I love http://discourse.org'
user.user_profile.save! user.user_profile.save!
user user
end end
it 'includes the link as nofollow if the user is not new' do it 'includes the link as nofollow if the user is not new' do
user.user_profile.send(:cook) user.user_profile.send(:cook)
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>") expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>") expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
end end
it 'removes the link if the user is new' do it 'removes the link if the user is new' do
user.trust_level = TrustLevel.levels[:newuser] user.trust_level = TrustLevel.levels[:newuser]
user_profile.send(:cook) user_profile.send(:cook)
expect(user_profile.bio_excerpt).to eq("im sissy and i love http://ponycorns.com") expect(user_profile.bio_excerpt).to match_html("I love http://discourse.org")
expect(user_profile.bio_processed).to eq("<p>im sissy and i love http://ponycorns.com</p>") expect(user_profile.bio_processed).to eq("<p>I love http://discourse.org</p>")
end end
context 'leader_links_no_follow is false' do context 'leader_links_no_follow is false' do
@ -90,22 +90,22 @@ describe UserProfile do
it 'includes the link without nofollow if the user is trust level 3 or higher' do it 'includes the link without nofollow if the user is trust level 3 or higher' do
user.trust_level = TrustLevel.levels[:leader] user.trust_level = TrustLevel.levels[:leader]
user_profile.send(:cook) user_profile.send(:cook)
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com'>http://ponycorns.com</a>") expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org'>http://discourse.org</a>")
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\">http://ponycorns.com</a></p>") expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\">http://discourse.org</a></p>")
end end
it 'removes nofollow from links in bio when trust level is increased' do it 'removes nofollow from links in bio when trust level is increased' do
created_user.change_trust_level!(:leader) created_user.change_trust_level!(:leader)
expect(created_user.user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com'>http://ponycorns.com</a>") expect(created_user.user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org'>http://discourse.org</a>")
expect(created_user.user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\">http://ponycorns.com</a></p>") expect(created_user.user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\">http://discourse.org</a></p>")
end end
it 'adds nofollow to links in bio when trust level is decreased' do it 'adds nofollow to links in bio when trust level is decreased' do
created_user.trust_level = TrustLevel.levels[:leader] created_user.trust_level = TrustLevel.levels[:leader]
created_user.save created_user.save
created_user.change_trust_level!(:regular) created_user.change_trust_level!(:regular)
expect(created_user.user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>") expect(created_user.user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
expect(created_user.user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>") expect(created_user.user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
end end
end end
@ -115,8 +115,8 @@ describe UserProfile do
it 'includes the link with nofollow if the user is trust level 3 or higher' do it 'includes the link with nofollow if the user is trust level 3 or higher' do
user.trust_level = TrustLevel.levels[:leader] user.trust_level = TrustLevel.levels[:leader]
user_profile.send(:cook) user_profile.send(:cook)
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>") expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>") expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
end end
end end
end end