浏览代码

companion: add stronger validation for urls sent via URL plugin

ifedapoolarewaju 5 年之前
父节点
当前提交
35d51b5d12

+ 30 - 3
packages/@uppy/companion/src/server/controllers/url.js

@@ -19,8 +19,7 @@ module.exports = () => {
  */
 const meta = (req, res) => {
   logger.debug('URL file import handler running', null, req.id)
-
-  if (!validator.isURL(req.body.url, { require_protocol: true, require_tld: !req.companion.options.debug })) {
+  if (!validateURL(req.body.url, req.companion.options.debug)) {
     logger.debug('Invalid request body detected. Exiting url meta handler.', null, req.id)
     return res.status(400).json({ error: 'Invalid request body' })
   }
@@ -42,6 +41,10 @@ const meta = (req, res) => {
  */
 const get = (req, res) => {
   logger.debug('URL file import handler running', null, req.id)
+  if (!validateURL(req.body.url, req.companion.options.debug)) {
+    logger.debug('Invalid request body detected. Exiting url import handler.', null, req.id)
+    return res.status(400).json({ error: 'Invalid request body' })
+  }
 
   utils.getURLMeta(req.body.url)
     .then(({ size }) => {
@@ -65,10 +68,34 @@ const get = (req, res) => {
       res.status(response.status).json(response.body)
     }).catch((err) => {
       logger.error(err, 'controller.url.get.error', req.id)
+      // @todo this should send back error (not err)
       res.json({ err })
     })
 }
 
+/**
+ * Validates that the download URL is secure
+ * @param {string} url the url to validate
+ * @param {boolean} debug whether the server is running in debug mode
+ */
+const validateURL = (url, debug) => {
+  const validURLOpts = {
+    protocols: ['http', 'https'],
+    require_protocol: true,
+    require_tld: !debug
+  }
+  if (!validator.isURL(url, validURLOpts)) {
+    return false
+  }
+
+  const parsed = utils.parseURL(url)
+  if (!validator.isFQDN(parsed.hostname, { require_tld: !debug })) {
+    return false
+  }
+
+  return true
+}
+
 /**
  * Downloads the content in the specified url, and passes the data
  * to the callback chunk by chunk.
@@ -81,7 +108,7 @@ const downloadURL = (url, onDataChunk, traceId) => {
   const opts = {
     uri: url,
     method: 'GET',
-    followAllRedirects: true
+    followAllRedirects: false
   }
 
   request(opts)

+ 12 - 0
packages/@uppy/companion/src/server/helpers/utils.js

@@ -1,4 +1,5 @@
 const request = require('request')
+const urlParser = require('url')
 const crypto = require('crypto')
 
 /**
@@ -41,6 +42,17 @@ exports.sanitizeHtml = (text) => {
   return text ? text.replace(/<\/?[^>]+(>|$)/g, '') : text
 }
 
+/**
+ * Node 6(and beyond) compatible url parser
+ * @todo drop the use of url.parse when support for node 6 is dropped
+ *
+ * @param {string} url URL to be parsed
+ */
+exports.parseURL = (url) => {
+  // eslint-disable-next-line
+  return urlParser.URL ? new urlParser.URL(url) : urlParser.parse(url)
+}
+
 /**
  * Gets the size and content type of a url's content
  *

+ 2 - 5
packages/@uppy/companion/src/standalone/index.js

@@ -1,11 +1,11 @@
 const express = require('express')
 const qs = require('querystring')
-const urlParser = require('url')
 const companion = require('../companion')
 const helmet = require('helmet')
 const morgan = require('morgan')
 const bodyParser = require('body-parser')
 const redis = require('../server/redis')
+const { parseURL } = require('../server/helpers/utils')
 const merge = require('lodash.merge')
 // @ts-ignore
 const promBundle = require('express-prom-bundle')
@@ -53,10 +53,7 @@ morgan.token('url', (req, res) => {
 morgan.token('referrer', (req, res) => {
   const ref = req.headers.referer || req.headers.referrer
   if (typeof ref === 'string') {
-    // @todo drop the use of url.parse
-    // when support for node 6 is dropped
-    // eslint-disable-next-line
-    const parsed = urlParser.URL ? new urlParser.URL(ref) : urlParser.parse(ref)
+    const parsed = parseURL(ref)
     const query = qs.parse(parsed.search.replace('?', ''));
     ['uppyAuthToken', 'access_token'].forEach(key => {
       if (query[key]) {