mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-11 01:54:40 +08:00
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:
parent
50068116d4
commit
06a1eb42b2
3 changed files with 109 additions and 1 deletions
75
spec-nylas/components/evented-iframe-spec.cjsx
Normal file
75
spec-nylas/components/evented-iframe-spec.cjsx
Normal 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()
|
||||
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue