Search code examples
apache.htaccesssecuritymod-rewritetoken

How secure is to append a secret token as query string in a htaccess rewrite rule?


I was doing some tests while trying to provide an answer to mod-rewrite redirect but prevent direct access.

Original question goal is basically mask example.com/public/foo as example.com/foo while forbidding access to the real URL example.com/public/foo.

Once I found a possible solution appending a token to the rewritten URL's Query String,

RewriteCond %{REQUEST_URI} !/public/
RewriteRule ^(.*)$ /public/$1?token=SECRET_TOKEN [L]

RewriteCond %{REQUEST_URI} /public/
RewriteCond %{QUERY_STRING} !token=SECRET_TOKEN
RewriteRule ^(.*)$ / [R=403,L]

I realized (with Chrome's DevTools) that I really can't see this token in the Request Headers.

Headers Screenshot

I understand that's because the first RewriteRule doesn't trigger a redirect on the client and all this rewrite process takes place in the server.

If I am right, this SECRET_TOKEN is secure, isn't it? (By secure, I mean it can't be known by the client in any way)


Solution

  • Ordinarily, the client should never see the SECRET_TOKEN - as you say, the rewrite is internal to the server.

    However, it's possible the SECRET_TOKEN could get exposed under certain conditions, depending on the server config.

    For example, using the code you posted, the SECRET_TOKEN could get exposed by requesting either example.com/public or example.com/subdir (where subdir is a subdirectory inside /public) - note the omission of the trailing slash.

    1. example.com/public - If you were to request /public (no trailing slash) then mod_dir "corrects" this by appending a trailing slash, which is achieved with a 301 external redirect. However, the redirect does not occur immediately and your RewriteRule directive ends up rewriting this request (since the REQUEST_URI variable is still /public at this point - no trailing slash) to /public/?token=SECRET_TOKEN (note the trailing slash now on the directory). (The URL-path does not appear to be updated by the RewriteRule for some reason, as you might expect this to be /public/public, however, the query string is still appended. Maybe something to do with the subrequest that mod_dir issues? Not sure why?) Since a 301 has already been triggered by mod_dir, this response (containing the SECRET_TOKEN) is now sent back to the user as an external redirect and the SECRET_TOKEN is exposed.

    2. example.com/subdir - A similar (but slightly different) thing would happen if there was a subdirectory /public/subdir and you requested /subdir (no trailing slash). Your RewriteRule directive kicks in first this time and internally rewrites the request to /public/subdir?token=SECRET_TOKEN. Ok so far. But then mod_dir kicks in and wants to append a trailing slash to /public/subdir - it does this with an external 301 redirect. So the request is now redirected to /public/subdir/?token=SECRET_TOKEN and the SECRET_TOKEN is exposed to the user (as well as the /public subdirectory, which was previously unknown).

    However, using the query string in this way does have some other caveats:

    1. As it stands, you are overwriting any other query string on the request.

    2. To merge an existing query string on the request, you would need to specify the QSA flag on the RewriteRule. However, if a user then appended ?token= on the request then they would override your token URL param if you wanted to read this using the $_GET superglobal in a PHP script. You could manually change the order of the query string parameters by explicitly including the QUERY_STRING server variable in the substitution instead of using the QSA flag - but this is a bit more messy (eg. what if there is no query string?).