VYPR
Moderate severityNVD Advisory· Published Aug 5, 2020· Updated Aug 4, 2024

CVE-2020-16254

CVE-2020-16254

Description

The Chartkick gem through 3.3.2 for Ruby allows Cascading Style Sheets (CSS) Injection (without attribute).

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Chartkick gem <=3.3.2 allows CSS injection via unsanitized width/height options, potentially leading to defacement or data theft.

Summary

The Chartkick gem for Ruby (version 3.3.2 and earlier) contains a CSS injection vulnerability in its chart rendering method [1][2]. The root cause is that the height and width options passed to chart helper methods were not validated or sanitized before being inserted into inline styles within an HTML style attribute. This allowed attackers to inject arbitrary CSS, potentially including keylogging via CSS-based attribute selectors or content exfiltration techniques that rely on CSS [3].

Exploitation

To exploit this, an attacker must be able to control the height or width parameters passed to a chart helper (e.g., line_chart or pie_chart). In typical Rails applications, these parameters may be supplied via user input or derived from untrusted sources if not carefully validated. The injection occurs server-side: the attacker's payload is reflected directly into the generated HTML without sanitization, so no client-side interaction beyond visiting the page is needed [4].

Impact

A successful CSS injection could allow an attacker to modify the visual appearance of the page (defacement), obscure legitimate content, or potentially steal sensitive information from the page by using CSS selectors that match specific attribute or text content patterns. While not as severe as full XSS, CSS injection can still lead to data leakage and undermine the integrity of the application's UI.

Mitigation

The vulnerability was fixed in Chartkick 3.3.3 by adding validation that restricts height and width to only alphanumeric characters and the percent sign, preventing arbitrary CSS property injection [4]. Users should upgrade to version 3.3.3 or later. No workaround is available for affected versions, so upgrading is the recommended action.

References

  • [1] Chartkick GitHub repository
  • [2] NVD entry for CVE-2020-16254
  • [3] Ruby advisory database entry
  • [4] Commit fixing the issue

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
chartkickRubyGems
< 3.4.03.4.0

Affected products

2

Patches

1
ba67ab5e603d

Fixed CSS injection with width and height options

https://github.com/ankane/chartkickAndrew KaneAug 4, 2020via ghsa
3 files changed · +59 14
  • CHANGELOG.md+4 0 modified
    @@ -1,3 +1,7 @@
    +## 3.4.0 (unreleased)
    +
    +- Fixed CSS injection with `width` and `height` options
    +
     ## 3.3.2 (2020-07-23)
     
     - Updated Chartkick.js to 3.2.1
    
  • lib/chartkick/helper.rb+19 6 modified
    @@ -41,8 +41,8 @@ def chartkick_chart(klass, data_source, **options)
           @chartkick_chart_id ||= 0
           options = chartkick_deep_merge(Chartkick.options, options)
           element_id = options.delete(:id) || "chart-#{@chartkick_chart_id += 1}"
    -      height = options.delete(:height) || "300px"
    -      width = options.delete(:width) || "100%"
    +      height = (options.delete(:height) || "300px").to_s
    +      width = (options.delete(:width) || "100%").to_s
           defer = !!options.delete(:defer)
           # content_for: nil must override default
           content_for = options.key?(:content_for) ? options.delete(:content_for) : Chartkick.content_for
    @@ -63,14 +63,27 @@ def chartkick_chart(klass, data_source, **options)
     
           # html vars
           html_vars = {
    -        id: element_id,
    -        height: height,
    -        width: width
    +        id: element_id
           }
           html_vars.each_key do |k|
             html_vars[k] = ERB::Util.html_escape(html_vars[k])
           end
    -      html = (options.delete(:html) || %(<div id="%{id}" style="height: %{height}; width: %{width}; text-align: center; color: #999; line-height: %{height}; font-size: 14px; font-family: 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif;">Loading...</div>)) % html_vars
    +
    +      # css vars
    +      css_vars = {
    +        height: height,
    +        width: width
    +      }
    +      css_vars.each_key do |k|
    +        # limit to alphanumeric and % for simplicity
    +        # this prevents things like calc() but safety is the priority
    +        raise ArgumentError, "Invalid #{k}" unless css_vars[k] =~ /\A[a-zA-Z0-9%]*\z/
    +        # we limit above, but escape for safety as fail-safe
    +        # to prevent XSS injection in worse-case scenario
    +        css_vars[k] = ERB::Util.html_escape(css_vars[k])
    +      end
    +
    +      html = (options.delete(:html) || %(<div id="%{id}" style="height: %{height}; width: %{width}; text-align: center; color: #999; line-height: %{height}; font-size: 14px; font-family: 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif;">Loading...</div>)) % html_vars.merge(css_vars)
     
           # js vars
           js_vars = {
    
  • test/chartkick_test.rb+36 8 modified
    @@ -80,20 +80,48 @@ def test_id_escaped
         assert_match "id=\"test-123&quot;\"", line_chart(@data, id: "test-123\"")
       end
     
    -  def test_height
    -    assert_match "height: 150px;", line_chart(@data, height: "150px")
    +  def test_height_pixels
    +    assert_match "height: 100px;", line_chart(@data, height: "100px")
       end
     
    -  def test_height_escaped
    -    assert_match "height: 150px&quot;;", line_chart(@data, height: "150px\"")
    +  def test_height_percent
    +    assert_match "height: 100%;", line_chart(@data, height: "100%")
       end
     
    -  def test_width
    -    assert_match "width: 80%;", line_chart(@data, width: "80%")
    +  def test_height_quote
    +    error = assert_raises(ArgumentError) do
    +      line_chart(@data, height: "150px\"")
    +    end
    +    assert_equal "Invalid height", error.message
    +  end
    +
    +  def test_height_semicolon
    +    error = assert_raises(ArgumentError) do
    +      line_chart(@data, height: "150px;background:123")
    +    end
    +    assert_equal "Invalid height", error.message
    +  end
    +
    +  def test_width_pixels
    +    assert_match "width: 100px;", line_chart(@data, width: "100px")
       end
     
    -  def test_width_escaped
    -    assert_match "width: 80%&quot;;", line_chart(@data, width: "80%\"")
    +  def test_width_percent
    +    assert_match "width: 100%;", line_chart(@data, width: "100%")
    +  end
    +
    +  def test_width_quote
    +    error = assert_raises(ArgumentError) do
    +      line_chart(@data, width: "80%\"")
    +    end
    +    assert_equal "Invalid width", error.message
    +  end
    +
    +  def test_width_semicolon
    +    error = assert_raises(ArgumentError) do
    +      line_chart(@data, width: "80%;background:123")
    +    end
    +    assert_equal "Invalid width", error.message
       end
     
       def test_nonce
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.