Validating Signed URLs with Vinyl Cache

Note

This Tips & Tricks article is unannounced and unfinished. It serves the purpose of providing example code for a talk for which insufficient preparation time was allocated.

Signed URLs are created by some other component, for example a CMS. Signed URLs can not be manipulated externally: Only URLs which are generated by the signing component are accepted (unless of course the secret / signing key is leaked).

Here, only the basic concept is demonstrated. Many useful variations exist:

  • Using other algorithms than SHA256, in particular asymmetric crypto

  • Shortening the signature for less security, but shorter URLs

  • Accepting unsigned query parameters after the signature

  • Checking additional signed query parameters, for example timestamps

  • Adding additional data to the signature, for example a session cookie, which personalizes the URL.

VCL

The following VCL requires these VMODs:

The VCL code:

import blob;
import blobdigest;
import re;

import std;

sub vcl_init {
        new signed_url_re = re.regex("^(/.*)[?&]sig=([-_A-Za-z0-9]{43})$");
        new signer = blobdigest.hmac(SHA256, :affed00f:);
}

sub vcl_recv {
        if (! signed_url_re.match(req.url)) {
                return (synth(404));
        }
        # log expected signature - remove for prod
        std.log(signed_url_re.backref(1) + "&sig=" +
                blob.encode(encoding=BASE64URLNOPAD,
                    blob=signer.hmac(blob.decode(
                        encoded=signed_url_re.backref(1)))));
        if (! blob.equal(
            blob.decode(decoding=BASE64URLNOPAD,
                encoded=signed_url_re.backref(2)),
            signer.hmac(blob.decode(
                encoded=signed_url_re.backref(1))))) {
                return (synth(403));
        }

        # remove the signature. Can be kept if the backend handles it
        set req.url = signed_url_re.backref(1);

        # here we would normally fall through to normal processing
        return (synth(200));
}

What is happening here:

signed_url_re is a regular expression for the expected req.url format: It needs to end on a sig query parameter with a 32 byte value as base64 with no padding (43 base64 characters).

signer is a SHA256 HMAC signer with the base64 secret affed00f, which obviously must not be used in production.

In vcl_recv, requests to URLs which do not have the required format are rejected with a 404 status. For a match, the provided signature is decoded and compared with the calculated signature. A 403 status is returned for a wrong signature.

Finally, the signature is stripped off.

References

This work is used as an example in the presentation WAF: Wrong Approach Firewall at GPN24.

Acknowledgements

Development of the VCL code and this documentation has been funded through an investment of the Sovereign Tech Agency.