Project

General

Profile

Actions

Docs ModSecDownload » History » Revision 34

« Previous | Revision 34/46 (diff) | Next »
nitrox, 2009-04-13 16:49
fix ugly formating pros/cons


Secure and Fast Downloading

Module: mod_secdownload

Description

There are multiple ways to handle secured download mechanisms:

  1. use the webserver and the internal HTTP authentication
  2. use the application to authenticate and send the file
    through the application

Both ways have limitations:

Webserver:
  • + fast download
  • + no additional system load
  • -- inflexible authentication handling

Application:

  • + integrated into the overall layout
  • + very flexible permission management
  • -- the download occupies an application thread/process

A simple way to combine the two ways could be:

1. app authenticates user and checks permissions to
download the file.
2. app redirects user to the file accessable by the webserver
for further downloading.
3. the webserver transfers the file to the user.

As the webserver doesn't know anything about the permissions
used in the app, the resulting URL would be available to every
user who knows the URL.

mod_secdownload removes this problem by introducing a way to
authenticate a URL for a specified time. The application has
to generate a token and a timestamp which are checked by the
webserver before it allows the file to be downloaded by the
webserver.

The generated URL has to have the format:

<uri-prefix>/<token>/<timestamp-in-hex>/<rel-path>
which looks like "yourserver.com/bf32df9cdb54894b22e09d0ed87326fc/435cc8cc/secure.tar.gz" 

<token> is an MD5 of

1. a secret string (user supplied)
2. <rel-path> (starts with /)
3. <timestamp-in-hex>

As you can see, the token is not bound to the user at all. The
only limiting factor is the timestamp which is used to
invalidate the URL after a given timeout (secdownload.timeout).

Important

Be sure to choose a another secret than the one used in the
examples, as this is the only part of the token that is not
known to the user.

Ensure that the token is also in hexadecimal. Depending on
the programming language you use, there might be no extra
step for this. For instance, in PHP, the MD5 function
returns the Hex value of the digest. If, however, you use a
language such as Java or Python, the extra step of converting
the digest into Hex is needed (see the Python example below).

If the user tries to fake the URL by choosing a random token,
status 403 'Forbidden' will be sent out.

If the timeout is reached, status 410 'Gone' will be
sent. This used to be 408 'Request Timeout' in earlier versions.

If token and timeout are valid, the <rel-path> is appended to
the configured (secdownload.document-root) and passed to the
normal internal file transfer functionality. This might lead to
status 200 or 404.

Options

secdownload.secret        = &lt;string&gt;
secdownload.document-root = &lt;string&gt;
secdownload.uri-prefix = &lt;string&gt; (default: /)
secdownload.timeout = &lt;short&gt; (default: 60 seconds)

Examples

Your application has to generate the correct URLs.

PHP Example

  <?php

  $secret = "verysecret";
  $uri_prefix = "/dl/";

  # filename
  # please note file name starts with "/" 
  $f = "/secret-file.txt";

  # current timestamp
  $t = time();

  $t_hex = sprintf("%08x", $t);
  $m = md5($secret.$f.$t_hex);

  # generate link
  printf('<a href="%s%s/%s%s">%s</a>',
         $uri_prefix, $m, $t_hex, $f, $f);
  ?>

Ruby On Rails example, used in the context of a helper

  def gen_sec_link(rel_path)
    rel_path.sub!(/^([^\/])/,'/\1')     # Make sure it had a leading slash
    s_secret = 'verysecret'             # Secret string
    uri_prefix = '/dl/'                 # Arbitrary download prefix
    timestamp = "%08x" % Time.now.to_i  # Timestamp, to hex
    token = MD5::md5(s_secret + rel_path + timestamp).to_s    # Token Creation
    '%s%s/%s%s' % [uri_prefix, token, timestamp, rel_path]   # Return the properly formatted string
  end

So in a view or helper:

<%= link_to "Private Image", gen_sec_link("path/from/download-area/someimage.img") %>

Python example, usable with Django or any other Python web framework

  def gen_sec_link(rel_path):
      import time, hashlib
      secret = 'verysecret'
      uri_prefix = '/dl/'
      hextime = "%08x" % time.time()
      token = hashlib.md5(secret + rel_path + hextime).hexdigest()
      return '%s%s/%s%s' % (uri_prefix, token, hextime, rel_path)

C# Example

//import library

using System.Security.Cryptography;
using System.Text;

//function :
    public string GetHash(string hashMe) // function get MD5
    {
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        UTF7Encoding encoder = new UTF7Encoding();
        Byte[] encStringBytes;
        encStringBytes = encoder.GetBytes(hashMe);
        encStringBytes = md5.ComputeHash(encStringBytes);
        string strHex = string.Empty;
        foreach (byte b in encStringBytes)       
            strHex += String.Format("{0:x2}", b);        
        return strHex;
    }
    public string GetCurrentEpochTimeInHex() // function get current epoch time in HEX
    {        
        DateTime EpochTime = new DateTime(1970, 1, 1, 0, 0, 0);
        TimeSpan diff = DateTime.UtcNow - EpochTime;
        return ((long)diff.TotalSeconds).ToString("x");
    }
    public string GenerateSecureLink(string rel_path) // ex : rel_path = "/secret-file.txt";
    {
        string t_hex = GetCurrentEpochTimeInHex();
        string serect = "verysecret";
        string uri_prefix = "/dl/";
        string m = GetHash(serect + rel_path + t_hex);
        return String.Format("{0}{1}/{2}{3}", uri_prefix, m, t_hex, rel_path);
    }

Reference make MD5 in C# : http://ok-cool.com/posts/read/125-php-md5-not-the-same-as-net-md5/

Webserver

The server has to be configured in the same way. The URI prefix and
secret have to match: ::


  server.modules = ( ..., "mod_secdownload", ... )

  secdownload.secret          = "verysecret" 
  secdownload.document-root   = "/home/www/servers/download-area/" 
  secdownload.uri-prefix      = "/dl/" 
  secdownload.timeout         = 10

Updated by nitrox over 15 years ago · 34 revisions