fix(iframe): catch relative and malformed uris

Summary:
Fixes T3252

When links were clicked with malformed, relative, or malicious href links
we'd perform default behavior instead of catching them.

If you have href="www.foo.bar" the browser by default thinks it's a
relative link. In our case it would prepend the full default base URI
which is file://path/to/edgehill. This would at best fail to do anything
and at worst execute an arbitrary file.

We now blacklist `file:` and check for the existence of a valid RFC 3986
schema on the URI.

Test Plan: manual

Reviewers: bengotow

Reviewed By: bengotow

Maniphest Tasks: T3252

Differential Revision: https://phab.nylas.com/D1888
This commit is contained in:
Evan Morikawa 2015-08-19 10:20:41 -07:00
parent 50068116d4
commit 06a1eb42b2
3 changed files with 109 additions and 1 deletions

View file

@ -0,0 +1,75 @@
React = require "react/addons"
ReactTestUtils = React.addons.TestUtils
EventedIFrame = require '../../src/components/evented-iframe'
describe 'EventedIFrame', ->
describe 'link clicking behavior', ->
beforeEach ->
@frame = ReactTestUtils.renderIntoDocument(
<EventedIFrame src="about:blank" />
)
@setAttributeSpy = jasmine.createSpy('setAttribute')
@preventDefaultSpy = jasmine.createSpy('preventDefault')
@openLinkSpy = jasmine.createSpy("openLink")
@oldOpenLink = atom.windowEventHandler.openLink
atom.windowEventHandler.openLink = @openLinkSpy
@fakeEvent = (href) =>
stopPropagation: ->
preventDefault: @preventDefaultSpy
target:
getAttribute: (attr) -> return href
setAttribute: @setAttributeSpy
afterEach ->
atom.windowEventHandler.openLink = @oldOpenLink
it 'works for acceptable link types', ->
hrefs = [
"http://nylas.com"
"https://www.nylas.com"
"mailto:evan@nylas.com"
"tel:8585311718"
"custom:www.nylas.com"
]
for href, i in hrefs
@frame._onIFrameClick(@fakeEvent(href))
expect(@setAttributeSpy).not.toHaveBeenCalled()
expect(@openLinkSpy).toHaveBeenCalled()
target = @openLinkSpy.calls[i].args[0].target
expect(target.getAttribute('href')).toBe href
it 'corrects relative uris', ->
hrefs = [
"nylas.com"
"www.nylas.com"
]
for href, i in hrefs
@frame._onIFrameClick(@fakeEvent(href))
expect(@setAttributeSpy).toHaveBeenCalled()
modifiedHref = @setAttributeSpy.calls[i].args[1]
expect(modifiedHref).toBe "http://#{href}"
it 'corrects protocol-relative uris', ->
hrefs = [
"//nylas.com"
"//www.nylas.com"
]
for href, i in hrefs
@frame._onIFrameClick(@fakeEvent(href))
expect(@setAttributeSpy).toHaveBeenCalled()
modifiedHref = @setAttributeSpy.calls[i].args[1]
expect(modifiedHref).toBe "https:#{href}"
it 'disallows malicious uris', ->
hrefs = [
"file://usr/bin/bad"
]
for href in hrefs
@frame._onIFrameClick(@fakeEvent(href))
expect(@preventDefaultSpy).toHaveBeenCalled()
expect(@openLinkSpy).not.toHaveBeenCalled()

View file

@ -1,4 +1,6 @@
React = require 'react'
{RegExpUtils}= require 'nylas-exports'
url = require 'url'
_ = require "underscore"
###
@ -83,14 +85,34 @@ class EventedIFrame extends React.Component
# The iFrame captures events that take place over it, which causes some
# interesting behaviors. For example, when you drag and release over the
# iFrame, the mouseup never fires in the parent window.
_onIFrameClick: (e) =>
e.stopPropagation()
target = @_getContainingTarget(e, {with: 'href'})
if target
# Sometimes urls can have relative, malformed, or malicious href
# targets. We test the existence of a valid RFC 3986 scheme and make
# sure the protocol isn't blacklisted. We never allow `file:` links
# through.
rawHref = target.getAttribute('href')
if @_isBlacklistedHref(rawHref)
e.preventDefault()
return
if not url.parse(rawHref).protocol
# Check for protocol-relative uri's
if (new RegExp(/^\/\//)).test(rawHref)
target.setAttribute('href', "https:#{rawHref}")
else
target.setAttribute('href', "http://#{rawHref}")
e.preventDefault()
atom.windowEventHandler.openLink(target: target)
_isBlacklistedHref: (href) ->
return (new RegExp(/^file:/i)).test(href)
_onIFrameMouseEvent: (event) =>
node = React.findDOMNode(@)
nodeRect = node.getBoundingClientRect()

View file

@ -12,4 +12,15 @@ RegExpUtils =
# https://regex101.com/r/zG7aW4/3
imageTagRegex: -> /<img\s+[^>]*src="([^"]*)"[^>]*>/g
# This tests for valid schemes as per RFC 3986
# We need both http: https: and mailto: and a variety of other schemes.
# This does not check for invalid usage of the http: scheme. For
# example, http:bad.com would pass. We do not check for
# protocol-relative uri's.
#
# Regex explanation here: https://regex101.com/r/nR2yL6/2
# See RFC here: https://tools.ietf.org/html/rfc3986#section-3.1
# SO discussion: http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative/31991870#31991870
hasValidSchemeRegex: -> new RegExp('^[a-z][a-z0-9+.-]*:', 'i')
module.exports = RegExpUtils