2
\$\begingroup\$

I need to pull an rss feed with links, headlines, and descriptions onto the front page of a website, and while I would like to think the source is trustworthy, you can never rule out malicious actors. I also would like to avoid iframes, since a sufficiently secure iframe sandbox would impact the browsing experience, and make the snippets less useful.

This is the parser/sanitizer I am testing, which relies on the built-in DOMParser (not regex ;) ) and I would love to know, not if the code is elegant, it isn't, but if it's reasonably xss safe. Tidying can come later. I've included a base test case, and a couple of test cases with a few simple xss exploits coded in. Run in Firefox Dev Edition 45 and Chrome 47, it catches what's there quite handily.

// Factory that returns an object that exposes a few 
// crude ways to pull data from a feed
var extractor = function (rss) {
  return {
    parser: new DOMParser(),
    // Parse items only when requested.
    get items () {
      if (!this._rawItems) {
        this._rawItems = this.parser.parseFromString(rss, 'application/xml').querySelectorAll("item");
      }
      return this._rawItems;
    },
    // Pull out one parsed item.
    item: function (index) {
      return this.items[index];
    },
    // Pull out the description. Parse to HTML without loading anything
    // to DOM then pull out text content, avoids several exploits.
    description: function(index) {
      return (
        this.parser
        .parseFromString(this.item(index).querySelector("description").textContent, "text/html")
        .body
        .textContent
      )
    },
    // Just extract title text. No contained html ever gets parsed.
    title: function(index) {
      return this.item(index).querySelector("title").textContent;
    },
    // Whitelist http and https protocols. Anything else is wiped
    url: function(index) {
      var url = this.item(index).querySelector("link").textContent;
      var protocol = url.split(':')[0];
      if (protocol === "http" || protocol === "https") return url;
      return ""
    }

  }
}

/* Test case follows: */

// Simple display builder, this represents the main use case, 
// Making sure to use safer DOM methods to construct, don't allow
// the live DOM to even touch the text strings.
// The only danger I can see is in the a href, which has already been
// whitelisted, but I'd love to know about any holes.
var makeHeadline = function(title, desc, url){
  var a = document.createElement('a');
  var h2 = document.createElement('h2');
  var p = document.createElement('p');
  a.setAttribute('href', url);
  h2.textContent = title;
  p.textContent = desc;
  a.appendChild(h2);
  a.appendChild(p);
  return a;
}

// Test data, template string for convenience, not production;
var feed = `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
  <title>RSS Title</title>
  <description>This is an example of an RSS feed</description>
  <link>http://www.example.com/main.html</link>
  <lastBuildDate>Mon, 06 Sep 2010 00:01:00 +0000 </lastBuildDate>
  <pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
  <ttl>1800</ttl>

  <item>
    <title>Example entry</title>
    <description>Here is some text containing an interesting description.</description>
    <link>http://www.example.com/blog/post/1</link>
    <guid isPermaLink="true">7bd204c6-1655-4c27-aeee-53f933c5395f</guid>
    <pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
  </item>

  <item>
    <title>
      <![CDATA[Another Example Entry]]>
    </title>
    <description><![CDATA[<p>This is a big html section</p><script>alert('it contains dangerous scripts')</script><img alt="<script>alert('and nasty tricks')</script>" src="bogus" onerror="alert('that could ruin your day')">]]></description>
    <link>javascript:alert('and a horrible js url')</link>
    <guid isPermaLink="true">7bd204c6-1655-4c27-aeee-53f933c5395f</guid>
    <pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
  </item>

  <item>
    <title>Just another link test</title>
    <description><p>It's more innocent</p><div style="position:fixed; background: white; width: 100vw; height: 100vh; top:0; left:0;">... Maybe</div></description>
    <link>http://example.com" onclick="alert('the horror!')</link>
    <guid isPermaLink="true">7bd204c6-1655-4c27-aeee-53f933c5395f</guid>
    <pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
  </item>

</channel>
</rss>`

var parsedFeed = extractor(feed);
var headline1 = makeHeadline(parsedFeed.title(0), parsedFeed.description(0), parsedFeed.url(0));
var headline2 = makeHeadline(parsedFeed.title(1), parsedFeed.description(1), parsedFeed.url(1));
var headline3 = makeHeadline(parsedFeed.title(2), parsedFeed.description(2), parsedFeed.url(2));
document.body.appendChild(headline1);
document.body.appendChild(headline2);
document.body.appendChild(headline3);
\$\endgroup\$
  • \$\begingroup\$ XSS prevention is a very broad topic. I would suggest using some existing XSS filtering library instead of rolling your own. I'm not even sure if this is suited for CodeReview or if it's more suited over at Security SE. \$\endgroup\$ – Joseph Jan 6 '16 at 23:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.