DansGuardian Documentation Wiki

You are here: Main Index » bypass_hash_usage


|

Wiki Information

Bypass Hash Usage


:!: DansGuardian's “bypass” functionality works for http: access, but of course not for https: (ssl/tls/443/encrypted) access. Generating and/or processing the hash outboard of DansGuardian doesn't change that; use of CGI reporting is not a way to relax the inability to bypass https: restrictions.

Why Bypass?

A DansGuardian feature called “bypass” provides a “click to acknowledge” capability. The user is told their access has been denied, but if they really want to they can go ahead and view the denied material anyway by clicking again. In effect DansGuardian asks “are you sure?”

DansGuardian's implementation falls into two parts which are largely separate:

  1. generating a “hash”, a long gobbledygook key which is used to skip filtering temporarily
  2. unlocking access when the right hash is provided to the filter

The longstanding DansGuardian feature unlocked one particular URL (http: only, not https:). At least as late as 2.10.0.2 DansGuardian “bypass” feature without extension did not unlock:

  • a whole website (but note recent change: possibly 2.10.0.3 and later do work this way)
  • everything following from one click
  • a webpage and everything it includes
  • a complete transaction (login, buy, etc.)

A couple of years ago only the topmost URL was likely to be denied, so unlocking only one particular URL like this used to work pretty well. But typical web usage has evolved so that now one click can result in tens of URL accesses, several of which may be denied even though they're deep inside a chain of requests. So in some environments the DansGuardian “bypass” feature as originally implemented is no longer viewed as all that useful.

A Newer/Simpler Way

Rather than bypassing DansGuardian by using some variant on the “hash” method as discussed below, it may be simplest to just use "multiple filter groups" to provide password-protected bypass functionality. This simpler way is especially relevant if you want either detailed logging or a different password for each user, as those additional requirements complicate use of the old bypass hash method. Once you get 'multiple filter groups' set up, likely you will find them very useful for many other purposes too. In other words you can think of being able to spread the initial setup cost of 'multiple filter groups' over several features besides just bypassing DansGuardian.

The old bypass hash method may still be a good alternative if:

  1. you routinely use anonymizelogs=on and are satisfied with it
  2. your users don't already have unique userids and passwords
  3. it's acceptable to you to have only one password for everyone (even though one person's letting that password escape may impact everyone else's security)
  4. it's acceptable to you to have users that aren't allowed to use the bypass functionality at all to nevertheless constantly be reminded that it exists for some other users

Several extensions have been discussed or made in the past:

Possible "Bypass" Extensions

This information may be mainly of historical interest. It has largely been superceded by the actuality of version 2.10.0.3 and later.

Typical extensions to the “bypass” functionality require most of:

  • ability to generate the hash yourself (including disabling “random” hash in DansGuardian)
  • using a CGI program (like “dansguardian.pl”) rather than the HTML template to deliver the “access denied” message
  • fancy file editing capabilities to securely and possibly remotely both insert and delete lines in configuration files (not just append lines, which is much simpler)

Although many of these extensions use the second part of DansGuardian's implementation –the “unlocking” capability–, they require the user to duplicate the first part of DansGuardian's implementation –“generating” the hash–. This begs the question: if DansGuardian already knows how to “generate” the hash, why duplicate this functionality?

Generating the hash yourself is necessary to keep a secret. The hash is in essence a plaintext-equivalent; if you have just the hash, you can unlock access to the page without anything else. Giving away the secret is not an issue on the regular “access denied” page, because all the user needs to do is click their mouse anyway; letting users see the hash doesn't give away anything that wasn't already possible anyway.

If use of the hash is protected though –for example by some kind of password– it's important not to let users see the hash until access is allowed. The hash can't be anywhere in the HTML –not even in a comment– because the user could see and copy it just with View:Source. So the program that's going to use the hash needs to generate it after access has been allowed. This is too late for DansGuardian to do the generation itself, so your program needs to duplicate the hash generation.

When DansGuardian's URL “unlock” behavior isn't quite right, extensions will need to simply modify the DansGuardian configuration instead of generating a hash key. (The idea of passing some sort of key into DansGuardian expecting it to do anything other than “unlock” a URL just won't work.) Modifying the DansGuardian configuration sounds very simple: just edit a few configuration files, then execute  dansguardian -g.

In fact implementing this capability can be rather tricky, possibly requiring some combination of additional software, fancy shell script coding, Perl, etc. Adding new lines to a configuration file is fairly easy (although the question of what should be done if the line conflicts with existing lines always lurks in the background). But deleting lines is harder. Which line should be deleted? What if the line to be deleted seems to exist more than once, or not at all? What if the file has been rearranged in the meantime so the added line is in the middle rather than at the end? What if the pattern exists inside a comment? What if something else has edited the file in the meantime and has changed the line slightly? Modifying lines would be even harder than inserting and deleting lines; fortunately all possible “bypass” extensions seem to be able to avoid needing this.

An additional problem is the “access denied” page and DansGuardian may be on different machines! One machine may be a generic internal webserver, the other machine may be a gateway or firewall. If so, changing the DansGuardian configuration will mean editing files remotely in a secure way. Secure remote execution can be a significant issue.

Extension: Single Global Password To Bypass

Require users not only to perform an additional click but also to enter a password (the same password for everybody).

Given the difficulty of keeping a global password from “escaping”, doing it this way is problematic at best.

The real requirement, more plainly stated, seems to be to allow some users to bypass but not others. A much better way to implement it is to assign different users to different filter groups (“multiple filter groups”). Then use a different HTML “access denied” template for each filter group, one of which includes a “bypass” hyperlink and the other of which doesn't. To present different HTML “access denied” templates to different filter groups, just use the htmltemplate=… option in the “dansguardianfN.conf” file for each filter group.

Doing it this way seems easier as it just leverages standard DansGuardian features without requiring any modification or extension (or to put it more bluntly: “no coding”). And once set up, “multiple filter groups” will most likely be useful for other purposes too.

Extension: Individual Passwords To Bypass

Require each user to not only perform an additional click but also to enter their own personal password.

The idea seems to be that increased identification of individuals and recording of those identities in a log might limit too-casual use of the “bypass” mechanism. Or to say the same thing another way, the though is that the perception of a more complete audit trail will make users think twice before using “bypass”.

But it seems to make just as much if not more sense to have a DansGuardian “auth” mechanism identify each individual user when they first use the web rather than waiting until they use “bypass”. That way all names will appear on every transaction in both the DansGuardian log and the HTML “access denied” template (which otherwise just names all users ””). You will actually have more of an audit trail this way, and everything will be in a single log file that doesn't have to be collated.

Extension: Unlock ENTIRE Site

Arguably this would work better than the longstanding bypass scheme, because a single unlocking would be quite a bit more likely to allow an entire transaction to complete. (It still wouldn't help if some references to another site were critical to completing the transaction and were also being denied. ) This sounds good, but so far there hasn't been a lot of empirical support that it's actually true.

There has even been some discussion about making the native DansGuardian “bypass” feature work this way. (REVISION early 2009) Both release notes and experience suggest DansGuardian's native “bypass” feature was in fact enhanced to work exactly this way in releases 2.10.0.3 and later.

If for some reason the revised native DansGuardian behavior can't be used, there are also several other ways to get this net behavior by extending the earlier DansGuardian bypass:

  • Let Users Except Sites
    Based on a user's action, change the “exceptionsitelist” configuration file. (It may be prudent to set up a mechanism for reviewing all such changes every day and backing out questionable changes.)
  • Temporarily Except Site For Everybody
    Simply add the bypass site to the right “exceptionsitelist” and do a gentle restart of DansGuardian. Then a little while later edit “exceptionsitelist” again to take the bypassed site back out and again do a gentle restart of DansGuardian.
     An implementation issue is if there are multiple filter groups, you need to figure out which filter group the bypass user is in to know which “exceptionurllist…” file to change. There's also the policy issue that it may not be okay to allow access to the site for everybody just because one user wanted to bypass something.
  • Temporarily Put Each Bypass User In New Filter Groups
    This may not be a reasonable implementation approach because the potential number of required filter groups can be unbounded. What if 60 users request bypasses simultaneously? What if 61? What if 62? What if 63? What if 64? …
  • Temporarily Put All Bypass Users In One Special Filter Group
    Suppose normally all users are in one of f1, f2, or f3. When a bypass is requested, temporarily move the user to f4, and arrange an exception only for filter group 4.
     This has the potential drawback that any bypass user also gets the benefit of all the other active bypasses; but this may not be a significant issue.

Extension: Temporarily Remove ALL Blocks For That Computer

Simply add the IP address of the requesting computer to “exceptioniplist” and do a gentle restart of DansGuardian. Then a little later edit “exceptioniplist” again to take the requesting computer's IP address back out and again do a gentle restart of DansGuardian.

This is guaranteed to allow the function to complete without any further blocks. It will also temporarily allow users to access unrelated material from other sites that really should be banned. Depending on your environment, this may be a “good” solution or it may be an “awful” solution.

Extension: Unlock Everything Triggered As A Result Of One Click

Although this sounds like a nice idea, nobody has any idea how to do it, either as an extension or by modifying the DansGuardian code.

Gotchas

Note well that the user variable in both the HTML Template File (-USER-) and as passed to a CGI Program ($in{'username'}) is not magic. It reflects DansGuardian's idea of who the user is, which is not necessarily the actual user identity and not the logon credentials. Often DansGuardian's idea of who the user is is simply blank, so the user variable will frequently turn out to be nothing more than an empty string. In many environments scripts that assume the username has a non-empty value will simply fail.

Relevant Forum Posts

FIXME This presentation –especially this rote repetition of some forum posts– is rather raw and unorganized and needs improving!

Subject: DansGuardian 2.8.0.6 is released
  • Added option so bypass hash could be enabled but no valid hash presented so external auth mechanisms can be used with the bypass feature.

Subject: Re: [dansguardian] Hash String Question

Not actually having used it myself, I offer the following advice:

  • Make sure you're using the HTML template file for your access denied page
  • Ensure that you do not have the -BYPASS- variable in the template file!
  • Have a button on the block page that redirects users who need to bypass the block to another page (PHP or CGI) that re-generates and hides the bypass HASH.

Subject: Recreating the Hash for use by Auxiliary Programs

Ernest W. Lessenger posted the following on creating the hash:

Basically, you set DG to use a PHP (perl, Cold Fusion, CGI, whatever) script as its blocked page. That page accepts a username and password and generates the bypass key from the information that DG sends in the URL.

I don't have time to write up a complete instruction set, though I do consult for a fee on this and other DG-related subjects. However, here's the general structure of the hash

        $magic = "the magic passphrase you set in DG";
        // Obviously this is completely impossible if you use the
        // random passphrase
        $unixtimekey = time() + 300;
 
 
        // If these lines are missing, some web browsers won't work
        // properly
        if (!eregi("https?://.+/.*", $url))
               $url = $url . "/";
 
 
        // This is the most important part.
        // The MD5 Hash MUST be hex encoded and uppercase with no
        // padding, salt, etc other than what is shown here
        // This can take some experimenting in Perl
        // DO NOT just use the time() function at this point
        $hash = strtoupper(md5($url . $magic . $clientip
                . $unixtimekey));
        $hash .= $unixtimekey;
 
        // If these lines are missing, some URL's won't work properly
        if (eregi("\?", $url))
               $bypass = $url . "&GBYPASS=" . $hash;
        else
               $ bypass = $url . "?GBYPASS=" . $hash;

If you have trouble with it, do a search for “hash” and “bypass” in the archives.

Subject: Password Protecting It

Daniel Barron posted the following on setting a password:

Well now. If you want to set a password, set the bypass config to 1 second and have some cgi, php or other program ask for user/pass. Upon a correct user/pass it should generate a bypass url.

Code snip to do this:

$ip = $ENV{'REMOTE_ADDR'};
 
if (!($url =~/^.*\/\/(.*?)([\/\:].*?)$/)) {
        $url = "$url/";
} $url =~ /^.*\/\/(.*?)([\/\:].*?)$/;
$host = $1;
$urlpart = $2;
$urlpart =~ s/\/$//g; if ($urlpart =~ m/\?/) {
        $bypass = '&GBYPASS=';
}
else {
        $bypass = '?GBYPASS=';
} $timekey = time() + $seconds; $hash = uc md5_hex($url . 'mysecret' . $ip . $timekey) . $timekey; print "Status: 302 Moved\n";
print "Connection: close\n";
print "Location: ${url}${bypass}${hash}\n\n";

Also remove the -BYPASS- from the template. Also set the secret to something you know not the auto generated one.

This has been covered on the list before but it's been a while since it was all together in 1 email. –
Daniel Barron

Subject: howto bypass filter with password the recommended way

Stefan Bauer shared his success on bypassing with a password

Use the reportinglevel = 3 and start with the regular HTML Template. Modify the HTML Template by inserting something like the following at an appropriate place (your IP address and full path will of course be different, and your wording and appearance may be different too):

...
<FORM id="myform" action="http://10.8.0.250:80/cgi-bin/passwords.cgi" method="post">
  <P align="center">
  Bypass DG
    <INPUT type="password" name="password" maxlength="12" size="12" />
    <INPUT type="hidden" name="sourceip" value="-IP-" />
    <INPUT type="hidden" name="username" value="-USER-" />
    <INPUT type="hidden" name="url" value="-URL-" />
    <INPUT type="submit"  value = "Submit" />
  <BR>
   <FONT size="1" FACE="arial, helvetica">
    The URL being accessed and your IP will be logged.
    <!-- not really, but it may look good ... -->
   </FONT>
  </P>
</FORM>
...

Then store the following passwords.pl file at /usr/lib/cgi-bin/passwords.pl to generate a valid hash inside the cgi-script (again your IP address and full path will of course be different). (I obtained this script from another user then modified it to suit my needs. Specifically I got rid of the logging features and shortened the script.)

#!/usr/bin/perl
use strict;
use CGI;
use Digest::MD5 qw(md5 md5_hex);
 
# Author: Kevin Hughes <kev@...>
# Date: 19 January 2006
#
# Date: 13 April 2009
# Stefan Bauer add generation of valid bypass-url
# stefan.bauer@...
 
 
# Set up the passwords that will guarantee access along with the
identification.
 
my %passwordlist = ( "dgbypass", "admin");
 
# Set up the URL for the denied page...
 
my $invalidurl = "http://10.8.0.250:80/denied.html";
 
# ===================================================
 
my $cgi = new CGI;
 
my $bypass = $cgi->param('bypasslink');
my $pass = $cgi->param('password');
my $sip = $cgi->param('sourceip');
my $name = $cgi->param('username');
my $url = $cgi->param('url');
my $magic = 'verysecret';
 
#Generating HASH for dgbypass
 
my $unixtime = time + 300;
my $hashstring = $url . $magic . $sip . $unixtime;
my $hex_hash = md5_hex $hashstring;
my $hash = uc($hex_hash . $unixtime);
my $bypass_url = $url . '&GBYPASS=' . $hash;
 
if (exists($passwordlist{$pass}))
{
# Password match
my $passname = $passwordlist{$pass};
print $cgi->redirect (-url =>$bypass_url);
}
else
{
# No password match. Go to the invalid password URL
print $cgi->redirect (-url =>$invalidurl);
}

And lastly edit your configuration as necessary so bypasskey = 'verysecret' in dansguardianfN.conf has exactly the same value as my $magic = 'verysecret'; in the above script.