MISC-5: Design the method of working with the anti-abuse scripts

Issue Information

Issue Type: Sub-task
Priority: Major
Status: Closed

Reported By:
Ben Tasker
Assigned To:
Ben Tasker
Project: Miscellaneous (MISC)
Resolution: Done (2015-05-21 16:26:16)
Affects Version: Bentasker.co.uk via Tor,
Target version: Bentasker.co.uk via Tor,

Created: 2015-05-21 12:31:03
Time Spent Working
Child of: MISC-2: Make bentasker.co.uk Available as Tor Hidden Service

This is consideration 3 in the parent issue.

Need to think about a method of letting the existing anti-abuse tools (for example Akeeba's Admin Tools) work with the .onion, preferably without risking a single bad actor temporarily blocking access for anyone visiting via the .onion.

One option would be to present the abuse script with an otherwise valid source IP so it can block that instead.

Ideally, that fake IP would be tied to a session in some way, so that the attacker would need to fully disconnect and re-connect.

I had wondered if the Tor client might re-use the same source port when forwarding onto Nginx for a given circuit (i.e. if there was some small element of NAT) but the port changes with each new request, so we'd only be blocking a keep-alive session if it were tied to the port (which might be an acceptable compromise).

The aim is to try and devise something sane which can be implemented at server level without needing to modify the protection scripts themselves.

Issue Links

Joomla plugin - fake_ip - on Github
Toggle State Changes


Assuming something consistent can be identified (taking for example the source-port if we're only interested in blocking a keep-alive session), most of the protection offered by these scripts happens at the application level, so writing a plugin to change the source IP won't break the connection (as it'll have no effect on the TCP stack).

The Reverse proxy can be configured to pass through whatever information, probably in a custom header.

It was mentioned in the tor-talk thread that it'd be nice if you could tie it to a circuit ID, but without hacking on the Tor source that's not currently possible.
Although I'm not surprised, looking at a tcpdump taken on the loopback adapter (so between the tor client and NGinx) there's nothing else we can use.

Killing off requests within a given keep-alive session doesn't offer much protection, but then IP bans in general aren't particularly effective, and so long as care is taken about how the 'source' IP is generated it does at least resolve the current risk of the .onion being susceptible to a fairly straightforward, low cost DoS.
Have been doing some modelling, and it should be possible to achieve what we need by using a /16.

For the PoC below I've followed Facebook's lead and dropped it into the DHCP ranges, but need to think about what range I want to use once this goes live.

One of the problems with banning on a source port basis is that it's still a relatively small namespace for an attacker who's actively trying to DoS the .onion. If they can place bannable requests on all 65535 ports (well, less than that as clients won't use the lower ones) then they've rendered the .onion inaccessible for the duration of the ban.

I don't want to have to lower the ban time within the tools themselves (as that'll mean it affects the www front as well).

So, I've come up with the following schema, based on the client's source-port, we generate an IP along the following lines
// For time, we deal in the nearest 10 minutes
$second = time();
$now = ceil($second/600)*600;
$min = date('i',$now);

// Calculate the fourth octet
$portcalc = round(($sport/258),1);
$oct4 = round($portcalc,0);

// Calculate the decimal section we're adding onto the third octet
// I'm not particularly happy with this bit
if ($oct4 > $portcalc){
        $dec = (($portcalc+1) - $oct4)*10;
        $dec = ($portcalc - $oct4)*10;

// Add the adjusted decimal onto the 10-minute indicator
$oct3 = round($min + $dec);

echo "Generated IP: $pref.$oct3.$oct4\n";

Adjusting the minute indicator with the decimal portion of the port calculation means that each 'IP' maps to a contiguous block of (at most) 26 possible source ports for a period of up to 10 minutes. The maths above is fairly simplistic, so there is technically scope for some overlap of 'IP' ranges, but it otherwise shows the basic concept

As long as the configured ban is less than an hour (I need to check) it means that a ban put in place will be valid for at least 1 minute but for no more than 10 minutes at a time.

No combination of time or source port (in the valid ranges) should cause an octet to be over 255 (as 65535/258 = 254.011), the highest IP that should be seen is (in the 50 minute block, source port 65519)
Will need to create a plugin to do the translation, but at the Reverse proxy end, added the following to the NGinx config to make sure what we need is available

proxy_set_header X-downstream-port $remote_port;

The name of the header above should probably also be considered semi-secret as it provides an easy way to circumvent a ban on the www front - send the correct header to denote that you're accessing via the onion and include a different IP in that header every time.

I'm not too worried about it, as IP bans are only really much use against automated scripts anyway (and I doubt anyone's going to be determined enough to adjust their script for me), but as a precaution will change the name of the header (and update this page - https://www.bentasker.co.uk/your-stored-data to ensure it's excluded).
Have created a plugin for Joomla that takes the above code snippet (almost verbatim) and runs it onAfterInitialise - configuration is through the plugin config, and it requires that the Onion indicator headers be present before it even begins to look at the source port.

It seems to be doing the job, the exceptions log shows exceptions from the correct range for my test requests.

Will push the plugin up to a github repo shortly.
Repo is up - https://github.com/bentasker/plg_fake_ip/blob/master/fake_onion_ip.php - there'll probably be a few tweaks to do at some point, but it's ready for a more thorough test.
Seems to be holding up OK, so going to mark this subtask as complete.
btasker changed status from 'Open' to 'Resolved'
btasker added 'Done' to resolution
btasker changed status from 'Resolved' to 'Closed'