After publishing an
article about the nginx module designed to combat bots, I received a lot of feedback in which people asked for Flash support.
I was sure that with due effort anyone could implement these functions on their own, as third-party applications, without changing the code of the module itself, but no one did, so I had to do PoC.
Initially, I wanted the module to be in the form of a designer, and no one has canceled the KISS principle, so it was decided to implement the entire client-side in the form of third-party applications. Nginx is doing well with proxying, so the easiest way is to write them as separate HTTP services.
This provides the following benefits:
- We write in any convenient language (I prefer Python)
- We are not tied to nginx, the quality of the code is not so important - it can be blocked, etc. etc.
- We can use the whole range of caching functionality nginx
- Even if the service is lying, all legitimate users who have already received their cookie are not aware of this - the main resource is available for them .
What had to add
For the main directive of
the testcookie-filter module , besides the previous values ​​of “on” and “off”, now another one has been added - “var”. If the module works in the “var” mode, then for this location:
- cookie check will be performed;
- nginx variables (all but $ testcookie_nexturl) are set;
- redirects, setting cookies and intercepting the request will not occur .
What is it for
Thus, you can now pass the correct cookie value to your application via the HTTP header:
location = /testcookie.swf { testcookie var; proxy_pass http://127.0.0.1:1234/; proxy_set_header Testcookie-Value $testcookie_set; proxy_set_header Testcookie-Valid $testcookie_ok; proxy_set_header Testcookie-Name "BPC"; }
Why through the header, and not through param in the case of Flash?
So the value does not appear in clear form in the client part, it can not be extracted, for example, by regexp.
')
And yet, Flash?
I believe that the method of installing cookies via Flash is not the most correct, but since users want it - why not do it.
I dismissed the idea to make a static SWF and substitute values ​​into it, like it was done in
Roboo , right away - easy to disassemble, difficult to modify, and so on.
Therefore, it was decided to give the user the opportunity to make life harder for the attacker.
For this, there are 2 files in the project:
cookie_encoder.py (Python):
def encode_cookie(cookie_value): key = 42 res = '' for x in cookie_value: res += chr(ord(x) ^ key) return res
cookie_decoder.as (ActionScript):
function flash_cookie_crypt_routine(str) { var result; for (var i = 0; i < str.length; i++) { result += String.fromCharCode(str.charCodeAt(i) ^ 42); } return result; } getURL("javascript:void(document.cookie='#TESTCOOKIE_NAME#=" + flash_cookie_crypt_routine("#TESTCOOKIE_VALUE#") + "');void(location.href='" + nexturl + "');");
As it is not difficult to guess, the first one is for encoding the value (in the example xory is 42), the second is for decoding on the client side (again xory is 42).
ActionScript is dynamically built into SWF using
libming . By the way, I had to patch it up a bit, so the project will work only with my fork, at least until the maintainers stop finding fault with the names of the functions and approve of my pull.
The service framework uses
fapws3 , a fast asynchronous web server for Python.
All together on the old coreduo, in one process, gives its ~ 3k req / s with concurency 1k, without any optimization and caching. In production, you can use the nginx proxy cache, so the SWF will be generated for each client only once, distributed by nginx and the service itself will be quite difficult to put.
Together
Configuration example:
server { listen 80; server_name domain.com; testcookie off; testcookie_name BPC; testcookie_secret keepmescret; testcookie_session $remote_addr; testcookie_arg attempt; testcookie_max_attempts 3; testcookie_fallback /cookies.html?backurl=http://$host$request_uri; testcookie_get_only on; testcookie_redirect_via_refresh on;
# connect testcookie.swf via swfobject (yes, I'm lazy)
# use testcookie_refresh_template directive,
# pass the value of $ testcookie_nexturl through param
testcookie_refresh_template '<html><body><script type="text/javascript" src="/swfobject.js"></script><script type="text/javascript">swfobject.embedSWF("/testcookie.swf", "cookie_installer", "100", "100", "9.0.0", "/expressInstall.swf", {"nexturl":"$testcookie_nexturl"});</script><div id="cookie_installer">welcome screen</div></body></html>';
# disable cookies
# for fallback URL, swfobject.js and expressInstall.swf
location = /cookies.html { root /var/www/public_html; } location = /swfobject.js { gzip on; gzip_min_length 1000; gzip_types text/plain; root /usr/local/nginx/root; } location = /expressInstall.swf { testcookie off; gzip on; gzip_min_length 1000; gzip_types text/plain; root /usr/local/nginx/root; }
# by this location the service dynamically generates SWF
location = /testcookie.swf {
# basic location with backend address
location / { testcookie on; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://127.0.0.1:8080; } }
Captcha is screwed in the same way.
Source texts
Source texts with installation instructions and documentation are available on
github under the BSD license.
Patches, add-ons, tests and bug reports are welcome.