CVE-2020-26298: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Redcarpet is a Ruby library for Markdown processing. In Redcarpet before version 3.5.1, there is an injection vulnerability which can enable a cross-site scripting attack. In affected versions no HTML escaping was being performed when processing quotes. This applies even when the `:escape_html` option was being used. This is fixed in version 3.5.1 by the referenced commit.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2021-01-11T19:15Z
Repository: https://github.com/vmg/redcarpet
Language: Ruby
Files changed: CHANGELOG.md, ext/redcarpet/html.c, lib/redcarpet.rb, redcarpet.gemspec, test/markdown_test.rb
Commit date: 2020-12-15 21:01:19 +0100
Commit message: Fix a security issue using `:quote` with `:escape_html`

Reported by @johan-smits.

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7203c6..4ff6958 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,14 @@
 # Changelog
 
+## Version 3.5.1 (Security)
+
+* Fix a security vulnerability using `:quote` in combination with the
+  `:escape_html` option.
+
+  Reported by *Johan Smits*.
+
 ## Version 3.5.0
 
 * Avoid mutating the options hash passed to a render object.
 
   Refs #663.
diff --git a/ext/redcarpet/html.c b/ext/redcarpet/html.c
index 805ddd8..785f780 100644
--- a/ext/redcarpet/html.c
+++ b/ext/redcarpet/html.c
@@ -253,12 +253,19 @@ static int
 rndr_quote(struct buf *ob, const struct buf *text, void *opaque)
 {
 	if (!text || !text->size)
 		return 0;
 
+	struct html_renderopt *options = opaque;
+
 	BUFPUTSL(ob, "<q>");
-	bufput(ob, text->data, text->size);
+
+	if (options->flags & HTML_ESCAPE)
+		escape_html(ob, text->data, text->size);
+	else
+		bufput(ob, text->data, text->size);
+
 	BUFPUTSL(ob, "</q>");
 
 	return 1;
 }
 
diff --git a/lib/redcarpet.rb b/lib/redcarpet.rb
index 4a494a4..c166147 100644
--- a/lib/redcarpet.rb
+++ b/lib/redcarpet.rb
@@ -1,10 +1,10 @@
 require 'redcarpet.so'
 require 'redcarpet/compat'
 
 module Redcarpet
-  VERSION = '3.5.0'
+  VERSION = '3.5.1'
 
   class Markdown
     attr_reader :renderer
   end
 
diff --git a/redcarpet.gemspec b/redcarpet.gemspec
index bb0453b..322782b 100644
--- a/redcarpet.gemspec
+++ b/redcarpet.gemspec
@@ -1,12 +1,12 @@
 # encoding: utf-8
 Gem::Specification.new do |s|
   s.name = 'redcarpet'
-  s.version = '3.5.0'
+  s.version = '3.5.1'
   s.summary = "Markdown that smells nice"
   s.description = 'A fast, safe and extensible Markdown to (X)HTML parser'
-  s.date = '2019-07-29'
+  s.date = '2020-12-15'
   s.email = 'vicent@github.com'
   s.homepage = 'http://github.com/vmg/redcarpet'
   s.authors = ["Natacha Porté", "Vicent Martí"]
   s.license = 'MIT'
   s.required_ruby_version = '>= 1.9.2'
diff --git a/test/markdown_test.rb b/test/markdown_test.rb
index 4347be9..68de125 100644
--- a/test/markdown_test.rb
+++ b/test/markdown_test.rb
@@ -218,10 +218,20 @@ class MarkdownTest < Redcarpet::TestCase
     refute render(text).include? '<q>quote</q>'
 
     assert_equal '<p>this is a <q>quote</q></p>', output
   end
 
+  def test_quote_flag_honors_escape_html
+    text = 'We are not "<svg/onload=pwned>"'
+
+    output_enabled  = render(text, with: [:quote, :escape_html])
+    output_disabled = render(text, with: [:quote])
+
+    assert_equal "<p>We are not <q>&lt;svg/onload=pwned&gt;</q></p>", output_enabled
+    assert_equal "<p>We are not <q><svg/onload=pwned></q></p>", output_disabled
+  end
+
   def test_that_fenced_flag_works
     text = <<-fenced.strip_heredoc
       This is a simple test
 
       ~~~~~

Vulnerable code: 	bufput(ob, text->data, text->size);
  VERSION = '3.5.0'
  s.version = '3.5.0'
  s.date = '2019-07-29'
Fixed code: ## Version 3.5.1 (Security)

* Fix a security vulnerability using `:quote` in combination with the
  `:escape_html` option.

  Reported by *Johan Smits*.

	struct html_renderopt *options = opaque;


	if (options->flags & HTML_ESCAPE)
		escape_html(ob, text->data, text->size);
	else
		bufput(ob, text->data, text->size);

  VERSION = '3.5.1'
  s.version = '3.5.1'
  s.date = '2020-12-15'
  def test_quote_flag_honors_escape_html
    text = 'We are not "<svg/onload=pwned>"'

    output_enabled  = render(text, with: [:quote, :escape_html])
    output_disabled = render(text, with: [:quote])

    assert_equal "<p>We are not <q>&lt;svg/onload=pwned&gt;</q></p>", output_enabled
    assert_equal "<p>We are not <q><svg/onload=pwned></q></p>", output_disabled
  end