如何限制Net :: HTTP请求的大小?

时间:2009-10-02 19:06:30

标签: ruby-on-rails ruby security

我正在创建一个API服务,允许人们向API调用提供图像的URL,然后服务下载图像进行处理。

如何确保有人不会给我一个像5MB图片的网址?有没有办法限制请求?

这是我到目前为止所掌握的,基本上可以抓住一切。

  req = Net::HTTP::Get.new(url.path)
  res = Net::HTTP.start(url.host, url.port) { |http|
    http.request(req)
  }

谢谢, 康拉德

4 个答案:

答案 0 :(得分:6)

不幸的是,{p> cwninja给了你一个只能用于意外攻击的答案。一个聪明的攻击者将毫无困难地击败那张支票。不应该使用他的方法有两个主要原因。首先,没有什么能保证HEAD响应中的信息与相应的GET响应相匹配。一个正常运行的服务器肯定会这样做,但恶意行为者不必遵循规范。攻击者可以简单地发送一个HEAD响应,该响应表明它的内容长度小于你的阈值,但随后在GET响应中提供了一个巨大的文件。但这甚至没有涵盖服务器使用Transfer-Encoding:chunked标头集发回响应的可能性。一个分块的反应很可能永远不会结束。即使您的HTTP客户端强制执行超时,一些人将您的服务器指向永无止境的响应可能会导致轻微的资源耗尽攻击。

要正确执行此操作,您需要使用一个HTTP库,该库允许您在收到字节时对其进行计数,如果超过阈值则中止。我可能会推荐Curb而不是Net :: HTTP。 (甚至可以使用Net :: HTTP执行此操作吗?)如果使用on_body和/或on_progress回调,则可以计算传入的字节数并在收到过大的文件时中止响应。显然,正如cwninja已经指出的那样,如果你收到的Content-Length标题大于你的阈值,你也想要中止。遏制也是notably faster than Net::HTTP

答案 1 :(得分:2)

首先尝试运行:

Net::HTTP.start(url.host, url.port) { |http|
  response = http.request_head(url.path)
  raise "File too big." if response['content-length'].to_i > 5*1024*1024
}

您仍然遇到竞争条件(有人可能在您执行HEAD请求后换出文件),但在简单的情况下,这会向服务器询问它将从{{1}发回的标头请求。

答案 2 :(得分:1)

另一种限制下载大小的方法(完整代码应检查响应状态,异常处理等。这只是一个例子)

Net::HTTP.start(uri.host, uri.port) do |http|
  request = Net::HTTP::Get.new uri.request_uri
  http.request request do |response|
# check response codes here
      body=''
      response.read_body do |chunk|
           body += chunk
           break if body.size > MY_SAFE_SIZE_LIMIT
      end
      break
  end
end

答案 3 :(得分:1)

结合其他两个答案,我想1)检查大小标题,2)观察块的大小,同时3)supporting https和4)积极执行超时。这是我提出的帮手:

require "net/http"
require 'uri'

module FetchUtil
  # Fetch a URL, with a given max bytes, and a given timeout
  def self.fetch_url url, timeout_sec=5, max_bytes=5*1024*1024
    uri = URI.parse(url)

    t0 = Time.now.to_f
    body = ''
    Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == 'https')) { |http|
      http.open_timeout = timeout_sec
      http.read_timeout = timeout_sec

      # First make a HEAD request and check the content-length
      check_res = http.request_head(uri.path)
      raise "File too big" if check_res['content-length'].to_i > max_bytes

      # Then fetch in chunks and bail on either timeout or max_bytes
      # (Note: timeout won't work unless bytes are streaming in...)
      http.request_get(uri.path) do |res|
        res.read_body do |chunk|
          raise "Timeout error" if (Time.now().to_f-t0 > timeout_sec)
          raise "Filesize exceeded" if (body.length+chunk.length > max_bytes)
          body += chunk
        end
      end
    }
    return body
  end
end