Last month I promised to talk about the exploits that James Snell uncovered which left feed readers vulnerable to some very annoying script-based attacks. I didn't want to provide details of the exploits until other feed readers had patched them, but now that James has published his test suites, I figure it's time to open the kimono. But before I go any further, I should first make two things clear:
- Every one of the vulnerabilities was fixed in FeedDemon 2.0.0.25.
- FeedDemon's newspapers operate in the Internet Zone rather than the local zone, so any script that makes it into a feed would not be able to access your local machine (ie: your data).
Now that that's out of the way, some details...
Like most feed readers, FeedDemon has always stripped potentially unsafe content from feeds, but James found several ways to get around this and was able to trick FeedDemon (and other feed readers) into displaying popups, toggling read status, and performing other annoying actions. The big "gotchas" that bit me were:
- I failed to handle whitespace in the word "javascript:" when used inside HTML attributes. Ex:
javas cript:window.alert('gotcha')
- I failed to handle HTML entities that aren't separated with semi-colons. Ex:
javasc
- I failed to cleanse the xml:base attribute in Atom feeds. Ex:
xml:base="javascript:window.alert('gotcha')"
At first glance it may seem that people would simply unsubscribe from any feed that contains an annoying exploit, but the risk goes beyond that. For example, an attacker could hijack a popular site (yes, it is possible) and inject malicious script into that site's feed. That way everyone subscribed to that feed would be exposed to the attack (ruining the site's reputation in the process).
The current crop of feed readers (including FeedDemon) protect from this with ultra-aggressive parsing and cleansing of feed content, but that's a never-ending battle. People will always find holes and exploit them, forcing aggregator developers to put out a steady stream of patches. I don't think any aggregator developer looks forward to that future, so let's come up with a better solution.
The real problem for desktop feed readers is that most of them can't simply disable script since they rely on JavaScript to interact with the browser (for example, FeedDemon's newspapers use JavaScript to change an item's appearance, among other things). However, it is possible to access the DOM through your application code directly, which means there is a way to block all script-based attacks without limiting the feed reader from interacting with the browser.
So, if you're developing a Windows-based feed reader which uses Internet Explorer's WebBrowser object, here's what I recommend:
- Enable the local zone lockdown feature to prevent script from accessing the local machine.
- Use the DLCTL_NO_SCRIPTS flag to completely disable scripting when viewing a page generated from your application. You can intercept the BeforeNavigate2 event to re-enable scripting before navigating to an external page.
- Access the DOM through application code via the various IHTMLDocument interfaces rather than through JavaScript (since script will no longer work after taking the previous step).
I've talked with a couple of aggregator developers about this approach, and they agree that it should work (and testing here shows that it does). You can expect FeedDemon to follow this approach soon.
As an aside, it may seem odd that I'd help my competitors by sharing this information, but I figure security flaws are an industry-wide problem, and not something that each developer should tackle alone. If we're going to prevent RSS from becoming as annoying as email, we need to work together on this.
FeedDemon rocks, Nick - and so do you.
Posted by: Sterling Camden | Thursday, September 07, 2006 at 04:49 PM
hmm, that's interesting. I knew it was possible to interact with the DOM of a webbrowser control, but I didn't realise you could receive events from it with scripting disabled.
very cool! :D
Posted by: Andrew Herron | Friday, September 08, 2006 at 02:10 AM
Hey Nick,
IE7's feed view is implemented basically just as you describe. We're working on a post on how we built it that may help out, but if you encounter any problems in your own implementation, just send me a mail.
Sean
Posted by: Sean Lyndersay | Friday, September 08, 2006 at 09:19 PM
Hi Nick,
First of all, thank you for sharing this information.
Did you try DLCTL_NO_SCRIPTS in combination with BeforeNavigate2 to turn scripting on and off depending on the page watched?
I tried to disable scripting in the AxWebBrowser control a long time ago, but found that it would only check that setting when the control is first created. The link you specified also states that this is only checked "when the WebBrowser Control is instantiated".
Do you know of a way around this?
Posted by: Luke Hutteman | Saturday, September 09, 2006 at 03:24 PM
Luke, I was able to get this working by adding a custom property ("DisableUnsafeContent") to a WebBrowser descendant, and when this property was changed, I used IOleControl.OnAmbientPropertyChange(DISPID_AMBIENT_DLCONTROL) to force the WebBrowser's Invoke event to fire (which updates the script setting).
I know you're using C#, but here's a snippet of my Delphi code in case it helps:
http://www.bradsoft.com/typepad/attach/delphi-wb-snippet.txt
I should add that I've only tested this with IE7 RC1, so I can't guarantee this will work with IE6.
Posted by: Nick Bradbury | Saturday, September 09, 2006 at 05:18 PM
Nick: I used to be a Delphi developer in a previous life so I should be able to figure it out - thanks!
Posted by: Luke Hutteman | Saturday, September 09, 2006 at 08:06 PM