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.
| Package | Affected versions | Patched versions |
|---|---|---|
chartkickRubyGems | < 3.4.0 | 3.4.0 |
Affected products
2- Ruby/Chartkick gemdescription
Patches
1ba67ab5e603dFixed CSS injection with width and height options
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"\"", 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";", 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%";", 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- github.com/advisories/GHSA-3j95-fjv2-3m4pghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-16254ghsaADVISORY
- github.com/ankane/chartkick/commit/ba67ab5e603de4d94676790fdac425f8199f1c4fghsaWEB
- github.com/ankane/chartkick/issues/546ghsax_refsource_MISCWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/chartkick/CVE-2020-16254.ymlghsaWEB
News mentions
0No linked articles in our index yet.