Skip to content

Nested Scripts #127

Closed
Closed
@myxoh

Description

@myxoh

Currently we are susceptible to a (slight) vulnerability with nested scripts:
Examples (using scrub_fragment):

Input: <script><script src='malicious.js'></script>
Sanitizer: strip
Output (text): &lt;script src='malicious.js'&gt;
Output (unescaped_text): <script src='malicious.js'>
(Sanitizer prune is immune to this)

Input: <<s>script src='malicious.js'>
Sanitizer: strip, prune
Output (text): &lt;script src='malicious.js'&gt;
Output (raw): <script src='malicious.js'>

Last example using strip or prune:
Input: <<s>script>alert('a')<<s>/script>
Output(raw): <script>alert('a')</script>

Why is this a problem?
I'm happy to discuss this, but I do believe that we should try to strip recursively as even though this outpus are only dangerous if unescaped, the whole purpose of scrubbing is to try to obtain the safest string back, and while <script src='malicious.js'> is not in itself unsafe, it is certainly less safe than it should be after going through a scrubber.

I've attached a PR
#128
with a potential solution using recursive scrubbing (information regarding this PR implementation is available on it)

Activity

flavorjones

flavorjones commented on Oct 22, 2017

@flavorjones
Owner

Thank you for reporting this. I'm looking at it now (apologies for the delay).

flavorjones

flavorjones commented on Oct 22, 2017

@flavorjones
Owner

I'm turning these into executable tests, and although I can easily reproduce the first example (which is a security problem), I cannot reproduce what you're seeing with the second or third.

Here's my code. Can you tell me what you're doing differently?

#!/usr/bin/env ruby

require 'loofah'
require 'yaml'

Nokogiri::VERSION_INFO.to_yaml
# => "---\n" +
#    "warnings: []\n" +
#    "nokogiri: 1.8.1\n" +
#    "ruby:\n" +
#    "  version: 2.4.1\n" +
#    "  platform: x86_64-linux\n" +
#    "  description: ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]\n" +
#    "  engine: ruby\n" +
#    "libxml:\n" +
#    "  binding: extension\n" +
#    "  source: packaged\n" +
#    "  libxml2_path: \"/home/flavorjones/.rvm/gems/ruby-2.4.1/gems/nokogiri-1.8.1/ports/x86_64-pc-linux-gnu/libxml2/2.9.5\"\n" +
#    "  libxslt_path: \"/home/flavorjones/.rvm/gems/ruby-2.4.1/gems/nokogiri-1.8.1/ports/x86_64-pc-linux-gnu/libxslt/1.1.30\"\n" +
#    "  libxml2_patches: []\n" +
#    "  libxslt_patches: []\n" +
#    "  compiled: 2.9.5\n" +
#    "  loaded: 2.9.5\n"

html = "<script><script src='malicious.js'></script>"
Loofah.fragment(html).scrub!(:strip).to_html # => "<script src='malicious.js'>"
Loofah.fragment(html).scrub!(:strip).to_text # => "&lt;script src='malicious.js'&gt;"


html = "<<s>script src='malicious.js'>"
Loofah.fragment(html).scrub!(:strip).to_html # => "&lt;<s>script src='malicious.js'&gt;</s>"
Loofah.fragment(html).scrub!(:strip).to_text # => "&lt;script src='malicious.js'&gt;"
Loofah.fragment(html).scrub!(:prune).to_html # => "&lt;<s>script src='malicious.js'&gt;</s>"
Loofah.fragment(html).scrub!(:prune).to_text # => "&lt;script src='malicious.js'&gt;"

html = "<<s>script>alert('a')<<s>/script>"
Loofah.fragment(html).scrub!(:strip).to_html # => "&lt;<s>script&gt;alert('a')&lt;<s>/script&gt;</s></s>"
Loofah.fragment(html).scrub!(:strip).to_text # => "&lt;script&gt;alert('a')&lt;/script&gt;"
Loofah.fragment(html).scrub!(:prune).to_html # => "&lt;<s>script&gt;alert('a')&lt;<s>/script&gt;</s></s>"
Loofah.fragment(html).scrub!(:prune).to_text # => "&lt;script&gt;alert('a')&lt;/script&gt;"
flavorjones

flavorjones commented on Oct 22, 2017

@flavorjones
Owner

My proposed fix for example 1 is in #132, here it is inline:

diff --git a/lib/loofah/scrubbers.rb b/lib/loofah/scrubbers.rb
index 508f6bf..982c593 100644
--- a/lib/loofah/scrubbers.rb
+++ b/lib/loofah/scrubbers.rb
@@ -99,7 +99,12 @@ module Loofah
 
       def scrub(node)
         return CONTINUE if html5lib_sanitize(node) == CONTINUE
-        node.before node.children
+        if node.children.length == 1 && node.children.first.cdata?
+          sanitized_text = Loofah.fragment(node.children.first.to_html).scrub!(:strip).to_html
+          node.before Nokogiri::XML::Text.new(sanitized_text, node.document)
+        else
+          node.before node.children
+        end
         node.remove
       end
     end
diff --git a/test/integration/test_ad_hoc.rb b/test/integration/test_ad_hoc.rb
index be4583f..1353966 100644
--- a/test/integration/test_ad_hoc.rb
+++ b/test/integration/test_ad_hoc.rb
@@ -157,6 +157,20 @@ mso-bidi-language:#0400;}
       assert_equal "", Loofah.scrub_document('<script>test</script>', :prune).text
     end
 
+    def test_nested_script_cdata_tags_should_be_scrubbed
+      html = "<script><script src='malicious.js'></script>"
+      stripped = Loofah.fragment(html).scrub!(:strip)
+      assert_empty stripped.xpath("//script")
+      refute_match("<script", stripped.to_html)
+    end
+
+    def test_nested_script_cdata_tags_should_be_scrubbed_2
+      html = "<script><script>alert('a');</script></script>"
+      stripped = Loofah.fragment(html).scrub!(:strip)
+      assert_empty stripped.xpath("//script")
+      refute_match("<script", stripped.to_html)
+    end
+
     def test_removal_of_all_tags
       html = <<-HTML
       What's up <strong>doc</strong>?

What do you think of that? And can you please help me understand if there's a security vulnerablity in either of the second or third example you've given?

myxoh

myxoh commented on Oct 23, 2017

@myxoh
Author

Sorry for the delay on replying. The issue I am reporting is when calling the .text method with encode_special_chars: false
While I am aware this is not meant to be 100% safe, I would expect that after I strip a string the result won't contain a script (even though it might contain partial unsafe texts).

Without fixing this behaviour for the cases in which encode_special_chars is false, the library ends up having only one layer of security (encoding characters) rendering the scrub effectively useless in this cases. This is particularly troubling as loofah_activerecord uses the text method, meaning that if I want to allow 'safe' tags (like breaklines) I am left with the nested-script vulnerability.

aaronchi

aaronchi commented on Nov 7, 2017

@aaronchi

We are seeing this issue as well. The inability of Loofah to handle proper escaping of nested tags is a problem that didn't exist with the deprecated Rails sanitizer and is not addressed by this proposed solution.

To reiterate what OP is pointing out in his second and third examples:

Input: <<test>script>alert('hi')<<test>/script>

Ouput in Rails deprecated sanitizer:
alert('hi')

Output using Loofah strip:
&lt;script&gt;alert('hi')&lt;/script&gt;
with encode_special_chars false:
<script>alert('hi')</script>

flavorjones

flavorjones commented on Nov 12, 2017

@flavorjones
Owner

@myxoh I've written code above twice to try to understand what you're seeing. Can you please write code back to help me understand?

@aaronchi Can you please write code as well to help me understand?

flavorjones

flavorjones commented on Nov 12, 2017

@flavorjones
Owner

@kaspth or @rafaelfranca can either of you help me understand?

flavorjones

flavorjones commented on Nov 12, 2017

@flavorjones
Owner

Is this related to rails/rails-html-sanitizer#48 ?

flavorjones

flavorjones commented on Nov 12, 2017

@flavorjones
Owner
flavorjones

flavorjones commented on Nov 12, 2017

@flavorjones
Owner

Wait, are you both saying that when you ask Loofah to not-escape entities, and you get them back unescaped, that you think it's a bug? I'm really struggling here to understand. Hopefully someone can help me, in particular with working code.

12 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @aaronchi@flavorjones@kaspth@myxoh

        Issue actions

          Nested Scripts · Issue #127 · flavorjones/loofah