When RSS Feeds Bite: How Chrome and Firefox Differ

Introduction: What Is RSS and XSLT?

RSS (Really Simple Syndication)

RSS is a standardized XML format used to publish and subscribe to frequently updated web content—news headlines, blog posts, announcements, and more. A typical RSS feed contains:

XSLT (eXtensible Stylesheet Language Transformations)

XSLT is a language for transforming XML documents into other text formats—typically HTML. By linking an XSLT stylesheet to an RSS feed via:

<?xml-stylesheet type="text/xsl" href="feed.xsl"?>

the browser or client can apply templates to render XML as styled HTML in the browser.

Browser Processing: Firefox vs. Chromium

Firefox’s Built-In RSS Viewer

Firefox detects RSS/XML feeds (text/xml or application/rss+xml) and applies an internal XSLT that escapes all embedded HTML entities. You see raw tags:

Firefox raw XML view
Firefox displays all HTML tags literally.

Chromium’s XML Rendering

Chromium-based browsers fetch the RSS as XML, apply the linked XSLT, and use disable-output-escaping="yes" to unescape entities. The result is injected via innerHTML, rendering the markup:

Chrome rendered HTML view
Chrome decodes and renders HTML inside the description.

Sample RSS + XSLT

Here’s a minimal example demonstrating the pivot:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="feed.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>Demo Feed</title>
    <item>
      <title>XSS Test</title>
      <description>&lt;h1&gt;Hello User&lt;/h1&gt;</description>
    </item>
  </channel>
</rss>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" indent="yes"/>
  <xsl:template match="rss/channel">
    <h1><xsl:value-of select="title"/></h1>
    <xsl:for-each select="item">
      <h2><xsl:value-of select="title"/></h2>
      <xsl:value-of select="description" disable-output-escaping="yes"/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

I Found This Vulnerability on a Real Website But i Can't Disclose it So Let's Imagine the Following Scenario

Scenario: Web App Using RSS for Data Storage

Imagine a web application that accepts user posts via RSS+XSLT as its storage and display mechanism. Contributors submit items, the server writes them to feed.xml, and clients render the feed directly. Without proper sanitization, any user can embed:

<description>&lt;script&gt;stealCookie()&lt;/script&gt;</description>

In Firefox, those tags appear inert. In Chrome, they execute on page load, stealing sessions or running arbitrary code.

Python Demo Application

Below is a Flask application that captures posts and writes to an RSS feed file. It includes endpoints to submit new items and view the feed:

from flask import Flask, Response, request
import os
from datetime import datetime

app = Flask(__name__)

temp_file = "temp"

# Ensure temp file exists
def init_temp():
    if not os.path.exists(temp_file):
        open(temp_file, 'w').close()

@app.route("/", methods=["GET"])
def rss_feed():
    init_temp()
    # Read any raw XML items from temp
    with open(temp_file, 'r', encoding='utf-8') as f:
        raw_items = f.read().strip()

    rss_content = f"""<?xml version=\"1.0\" encoding=\"utf-8\"?>
<?xml-stylesheet type='text/xsl' href='rss.xsl'?>
<rss version=\"2.0\">  
  <channel>
    <title>Course 1</title>
    <link>https://example.com</link>
    <description>Course 1 Announcements RSS Feed</description>
    <ttl>60</ttl>
{raw_items}
  </channel>
</rss>"""
    return Response(rss_content, mimetype='text/xml')

@app.route("/rss.xsl", methods=["GET"])
def rss_stylesheet():
    xsl_content = """<?xml version=\"1.0\" encoding=\"utf-8\"?>
<xsl:stylesheet version=\"1.0\"
  xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">  
  <xsl:output method=\"html\" indent=\"yes\"/>
  <xsl:template match=\"rss/channel\">
    <html>
      <head>
        <title><xsl:value-of select=\"title\"/></title>
      </head>
      <body>
        <h1><xsl:value-of select=\"title\"/></h1>
        <div><xsl:value-of select=\"description\"/></div>
        <xsl:for-each select=\"item\">
          <div style=\"border:1px solid #ccc; margin:1em 0; padding:1em;\">
            <h2><xsl:value-of select=\"title\"/></h2>
            <xsl:value-of select=\"description\" disable-output-escaping=\"yes\"/>
          </div>
        </xsl:for-each>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>"""
    return Response(xsl_content, mimetype='text/xsl')

@app.route("/add", methods=["POST"])
def add_item():
    init_temp()
    # Expect raw <item>...</item> XML in request body
    xml_item = request.data.decode('utf-8').strip()
    if xml_item.startswith('<item') and xml_item.endswith('</item>'):
        with open(temp_file, 'a', encoding='utf-8') as f:
            f.write(xml_item + '\n')
        return Response('Item added', status=201)
    else:
        return Response('Invalid item XML', status=400)

if __name__ == "__main__":
    app.run(debug=True)

Demo Video: Chrome vs. Firefox Behavior

This video demonstrates how RSS feeds are rendered differently in Chrome and Firefox when HTML entities are present in the XML description field.

Attack Scenarios & Mitigations

Attack Vectors

  1. Stored XSS: Scripts run in victim’s browser when loading feed.
  2. CSRF: Use image tags to trigger state-changing endpoints.
  3. Phishing: Embed fake forms or overlays in feed.

Mitigation Strategies