From 9009622d55e5442985b14895d4d9546931cd0fad Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk Date: Fri, 12 Apr 2024 00:30:46 +0200 Subject: [PATCH 01/15] - Change to include user-agent when generating - Use X-REAL-IP and then CLIENT_IP as a fallback --- README.md | 104 +++++++++++++++++++++++++--------------- ngx_http_js_challenge.c | 73 ++++++++++++++++++++-------- 2 files changed, 119 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index c54472b..325416d 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,66 @@ +# ngx_http_js_challenge_module -## ngx_http_js_challenge_module - -![GitHub](https://img.shields.io/github/license/simon987/ngx_http_js_challenge_module.svg) +[![GitHub License](https://img.shields.io/github/license/simon987/ngx_http_js_challenge_module.svg)](LICENSE) [![CodeFactor](https://www.codefactor.io/repository/github/simon987/ngx_http_js_challenge_module/badge)](https://www.codefactor.io/repository/github/simon987/ngx_http_js_challenge_module) +[![Demo Website](https://img.shields.io/badge/demo-website-blue.svg)](https://ngx-js-demo.simon987.net/) +Simple JavaScript proof-of-work based access control for Nginx, designed to provide security with minimal overhead. -[Demo website](https://ngx-js-demo.simon987.net/) +## Features -Simple javascript proof-of-work based access for Nginx with virtually no overhead. +- **Lightweight Integration:** Easy to integrate with existing Nginx installations. +- **Configurable Security:** Flexible settings to adjust security strength and client experience. +- **Minimal Performance Impact:** Designed to operate with virtually no additional server load. -Easy installation: just add `load_module /path/to/ngx_http_js_challenge_module.so;` to your -`nginx.conf` file and follow the [configuration instructions](#configuration). +## Quick Start -

- -

+1. **Installation** + Add the following line to your `nginx.conf`: + ``` + load_module /path/to/ngx_http_js_challenge_module.so; + ``` -### Configuration +2. **Configuration** + Use the simple or advanced configurations provided below to customize the module to your needs. -**Simple configuration** -```nginx +## Installation + +To install the ngx_http_js_challenge_module, follow these steps: + +1. Add the module loading directive to your Nginx configuration file (`/etc/nginx/nginx.conf`): + ``` + load_module /path/to/ngx_http_js_challenge_module.so; + ``` + +2. Apply the changes by reloading Nginx: + ``` + nginx -s reload + ``` + +## Configuration + +### Basic Configuration + +For basic setup, update your server block as follows: + +``` server { js_challenge on; - js_challenge_secret "change me!"; - - # ... + js_challenge_secret "change me!"; # Ensure to replace this with a strong secret in production } ``` +### Advanced Configuration -**Advanced configuration** -```nginx +For more complex setups, including exemptions for specific paths: + +``` server { js_challenge on; js_challenge_secret "change me!"; - js_challenge_html /path/to/body.html; + js_challenge_html "/path/to/body.html"; js_challenge_bucket_duration 3600; - js_challenge_title "Verifying your browser..."; + js_challenge_title "Verifying your browser```"; location /static { js_challenge off; @@ -45,49 +69,51 @@ server { location /sensitive { js_challenge_bucket_duration 600; - #... + # Add further customization here } - - #... } ``` -* `js_challenge on|off` Toggle javascript challenges for this config block -* `js_challenge_secret "secret"` Secret for generating the challenges. DEFAULT: "changeme" -* `js_challenge_html "/path/to/file.html"` Path to html file to be inserted in the `` tag of the interstitial page -* `js_challenge_title "title"` Will be inserted in the `` tag of the interstitial page. DEFAULT: "Verifying your browser..." -* `js_challenge_bucket_duration time` Interval to prompt js challenge, in seconds. DEFAULT: 3600 +### Parameters + +- **js_challenge on|off** Toggle javascript challenges for this config block +- **js_challenge_secret "secret"** Secret for generating the challenges. DEFAULT: "changeme" +- **js_challenge_html "/path/to/file.html"** Path to html file to be inserted in the `<body>` tag of the interstitial page +- **js_challenge_title "title"** Will be inserted in the `<title>` tag of the interstitial page. DEFAULT: "Verifying your browser```" +- **js_challenge_bucket_duration time** Interval to prompt js challenge, in seconds. DEFAULT: 3600 ### Installation 1. Add `load_module ngx_http_js_challenge_module.so;` to `/etc/nginx/nginx.conf` -1. Reload `nginx -s reload` +2. Reload `nginx -s reload` ### Build from source These steps have to be performed on machine with compatible configuration (same nginx, glibc, openssl version etc.) 1. Install dependencies - ```bash + ``` apt install libperl-dev libgeoip-dev libgd-dev libxslt1-dev libpcre3-dev ``` 2. Download nginx tarball corresponding to your current version (Check with `nginx -v`) - ```bash - wget https://nginx.org/download/nginx-1.16.1.tar.gz - tar -xzf nginx-1.16.1.tar.gz - export NGINX_PATH=$(pwd)/nginx-1.16.1/ + ``` + wget https://nginx.org/download/nginx-1.25.4.tar.gz + tar -xzf nginx-1.25.4.tar.gz + export NGINX_PATH=$(pwd)/nginx-1.25.4/ ``` 3. Compile the module - ```bash + ``` git clone https://github.com/simon987/ngx_http_js_challenge_module cd ngx_http_js_challenge_module ./build.sh ``` 4. The dynamic module can be found at `${NGINX_PATH}/objs/ngx_http_js_challenge_module.so` - - -### Known limitations / TODO +### Known limitations (To Do) * Users with cookies disabled will be stuck in an infinite refresh loop (TODO: redirect with a known query param, if no cookie is specified but the query arg is set, display an error page) -* If nginx is behind a reverse proxy/load balancer, the same challenge will be sent to different users and/or the response cookie will be invalidated when the user is re-routed to another server. (TODO: use the x-real-ip header when available) + +### Throughput +<p align="center"> + <img width="600px" src="throughput.png"/> +</p> \ No newline at end of file diff --git a/ngx_http_js_challenge.c b/ngx_http_js_challenge.c index 5adf14d..1572ddb 100644 --- a/ngx_http_js_challenge.c +++ b/ngx_http_js_challenge.c @@ -18,7 +18,7 @@ "<body>" \ "<script>" \ "window.onload=function(){!function(){function t(t){t?(f[0]=f[16]=f[1]=f[2]=f[3]=f[4]=f[5]=f[6]=f[7]=f[8]=f[9]=f[10]=f[11]=f[12]=f[13]=f[14]=f[15]=0,this.blocks=f):this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],this.h0=1732584193,this.h1=4023233417,this.h2=2562383102,this.h3=271733878,this.h4=3285377520,this.block=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0}var h=\"object\"==typeof window?window:{},s=!h.JS_SHA1_NO_NODE_JS&&\"object\"==typeof process&&process.versions&&process.versions.node;s&&(h=global);var i=!h.JS_SHA1_NO_COMMON_JS&&\"object\"==typeof module&&module.exports,e=\"function\"==typeof define&&define.amd,r=\"0123456789abcdef\".split(\"\"),o=[-2147483648,8388608,32768,128],n=[24,16,8,0],a=[\"hex\",\"array\",\"digest\",\"arrayBuffer\"],f=[],u=function(h){return function(s){return new t(!0).update(s)[h]()}},c=function(){var h=u(\"hex\");s&&(h=p(h)),h.create=function(){return new t},h.update=function(t){return h.create().update(t)};for(var i=0;i<a.length;++i){var e=a[i];h[e]=u(e)}return h},p=function(t){var h=eval(\"require('crypto')\"),s=eval(\"require('buffer').Buffer\"),i=function(i){if(\"string\"==typeof i)return h.createHash(\"s1\").update(i,\"utf8\").digest(\"hex\");if(i.constructor===ArrayBuffer)i=new Uint8Array(i);else if(void 0===i.length)return t(i);return h.createHash(\"s1\").update(new s(i)).digest(\"hex\")};return i};t.prototype.update=function(t){if(!this.finalized){var s=\"string\"!=typeof t;s&&t.constructor===h.ArrayBuffer&&(t=new Uint8Array(t));for(var i,e,r=0,o=t.length||0,a=this.blocks;r<o;){if(this.hashed&&(this.hashed=!1,a[0]=this.block,a[16]=a[1]=a[2]=a[3]=a[4]=a[5]=a[6]=a[7]=a[8]=a[9]=a[10]=a[11]=a[12]=a[13]=a[14]=a[15]=0),s)for(e=this.start;r<o&&e<64;++r)a[e>>2]|=t[r]<<n[3&e++];else for(e=this.start;r<o&&e<64;++r)(i=t.charCodeAt(r))<128?a[e>>2]|=i<<n[3&e++]:i<2048?(a[e>>2]|=(192|i>>6)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):i<55296||i>=57344?(a[e>>2]|=(224|i>>12)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):(i=65536+((1023&i)<<10|1023&t.charCodeAt(++r)),a[e>>2]|=(240|i>>18)<<n[3&e++],a[e>>2]|=(128|i>>12&63)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]);this.lastByteIndex=e,this.bytes+=e-this.start,e>=64?(this.block=a[16],this.start=e-64,this.hash(),this.hashed=!0):this.start=e}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%%4294967296),this}},t.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var t=this.blocks,h=this.lastByteIndex;t[16]=this.block,t[h>>2]|=o[3&h],this.block=t[16],h>=56&&(this.hashed||this.hash(),t[0]=this.block,t[16]=t[1]=t[2]=t[3]=t[4]=t[5]=t[6]=t[7]=t[8]=t[9]=t[10]=t[11]=t[12]=t[13]=t[14]=t[15]=0),t[14]=this.hBytes<<3|this.bytes>>>29,t[15]=this.bytes<<3,this.hash()}},t.prototype.hash=function(){var t,h,s=this.h0,i=this.h1,e=this.h2,r=this.h3,o=this.h4,n=this.blocks;for(t=16;t<80;++t)h=n[t-3]^n[t-8]^n[t-14]^n[t-16],n[t]=h<<1|h>>>31;for(t=0;t<20;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i&e|~i&r)+o+1518500249+n[t]<<0)<<5|o>>>27)+(s&(i=i<<30|i>>>2)|~s&e)+r+1518500249+n[t+1]<<0)<<5|r>>>27)+(o&(s=s<<30|s>>>2)|~o&i)+e+1518500249+n[t+2]<<0)<<5|e>>>27)+(r&(o=o<<30|o>>>2)|~r&s)+i+1518500249+n[t+3]<<0)<<5|i>>>27)+(e&(r=r<<30|r>>>2)|~e&o)+s+1518500249+n[t+4]<<0,e=e<<30|e>>>2;for(;t<40;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i^e^r)+o+1859775393+n[t]<<0)<<5|o>>>27)+(s^(i=i<<30|i>>>2)^e)+r+1859775393+n[t+1]<<0)<<5|r>>>27)+(o^(s=s<<30|s>>>2)^i)+e+1859775393+n[t+2]<<0)<<5|e>>>27)+(r^(o=o<<30|o>>>2)^s)+i+1859775393+n[t+3]<<0)<<5|i>>>27)+(e^(r=r<<30|r>>>2)^o)+s+1859775393+n[t+4]<<0,e=e<<30|e>>>2;for(;t<60;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i&e|i&r|e&r)+o-1894007588+n[t]<<0)<<5|o>>>27)+(s&(i=i<<30|i>>>2)|s&e|i&e)+r-1894007588+n[t+1]<<0)<<5|r>>>27)+(o&(s=s<<30|s>>>2)|o&i|s&i)+e-1894007588+n[t+2]<<0)<<5|e>>>27)+(r&(o=o<<30|o>>>2)|r&s|o&s)+i-1894007588+n[t+3]<<0)<<5|i>>>27)+(e&(r=r<<30|r>>>2)|e&o|r&o)+s-1894007588+n[t+4]<<0,e=e<<30|e>>>2;for(;t<80;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i^e^r)+o-899497514+n[t]<<0)<<5|o>>>27)+(s^(i=i<<30|i>>>2)^e)+r-899497514+n[t+1]<<0)<<5|r>>>27)+(o^(s=s<<30|s>>>2)^i)+e-899497514+n[t+2]<<0)<<5|e>>>27)+(r^(o=o<<30|o>>>2)^s)+i-899497514+n[t+3]<<0)<<5|i>>>27)+(e^(r=r<<30|r>>>2)^o)+s-899497514+n[t+4]<<0,e=e<<30|e>>>2;this.h0=this.h0+s<<0,this.h1=this.h1+i<<0,this.h2=this.h2+e<<0,this.h3=this.h3+r<<0,this.h4=this.h4+o<<0},t.prototype.hex=function(){this.finalize();var t=this.h0,h=this.h1,s=this.h2,i=this.h3,e=this.h4;return r[t>>28&15]+r[t>>24&15]+r[t>>20&15]+r[t>>16&15]+r[t>>12&15]+r[t>>8&15]+r[t>>4&15]+r[15&t]+r[h>>28&15]+r[h>>24&15]+r[h>>20&15]+r[h>>16&15]+r[h>>12&15]+r[h>>8&15]+r[h>>4&15]+r[15&h]+r[s>>28&15]+r[s>>24&15]+r[s>>20&15]+r[s>>16&15]+r[s>>12&15]+r[s>>8&15]+r[s>>4&15]+r[15&s]+r[i>>28&15]+r[i>>24&15]+r[i>>20&15]+r[i>>16&15]+r[i>>12&15]+r[i>>8&15]+r[i>>4&15]+r[15&i]+r[e>>28&15]+r[e>>24&15]+r[e>>20&15]+r[e>>16&15]+r[e>>12&15]+r[e>>8&15]+r[e>>4&15]+r[15&e]},t.prototype.toString=t.prototype.hex,t.prototype.digest=function(){this.finalize();var t=this.h0,h=this.h1,s=this.h2,i=this.h3,e=this.h4;return[t>>24&255,t>>16&255,t>>8&255,255&t,h>>24&255,h>>16&255,h>>8&255,255&h,s>>24&255,s>>16&255,s>>8&255,255&s,i>>24&255,i>>16&255,i>>8&255,255&i,e>>24&255,e>>16&255,e>>8&255,255&e]},t.prototype.array=t.prototype.digest,t.prototype.arrayBuffer=function(){this.finalize();var t=new ArrayBuffer(20),h=new DataView(t);return h.setUint32(0,this.h0),h.setUint32(4,this.h1),h.setUint32(8,this.h2),h.setUint32(12,this.h3),h.setUint32(16,this.h4),t};var y=c();i?module.exports=y:(h.s1=y,e&&define(function(){return y}))}();" \ - "const a0_0x2a54=['%s','res=','array'];(function(_0x41abf3,_0x2a548e){const _0x4457dc=function(_0x804ad2){while(--_0x804ad2){_0x41abf3['push'](_0x41abf3['shift']());}};_0x4457dc(++_0x2a548e);}(a0_0x2a54,0x178));const a0_0x4457=function(_0x41abf3,_0x2a548e){_0x41abf3=_0x41abf3-0x0;let _0x4457dc=a0_0x2a54[_0x41abf3];return _0x4457dc;};let c=a0_0x4457('0x2');let i=0x0;let n1=parseInt('0x'+c[0x0]);while(!![]){let s=s1[a0_0x4457('0x1')](c+i);if(s[n1]===0xb0&&s[n1+0x1]===0xb){document['cookie']=a0_0x4457('0x0')+c+i+'; path=/';break;}i++;};" \ + "const a0_0x2a54=['%s','challenge_token=','array'];(function(_0x41abf3,_0x2a548e){const _0x4457dc=function(_0x804ad2){while(--_0x804ad2){_0x41abf3['push'](_0x41abf3['shift']());}};_0x4457dc(++_0x2a548e);}(a0_0x2a54,0x178));const a0_0x4457=function(_0x41abf3,_0x2a548e){_0x41abf3=_0x41abf3-0x0;let _0x4457dc=a0_0x2a54[_0x41abf3];return _0x4457dc;};let c=a0_0x4457('0x2');let i=0x0;let n1=parseInt('0x'+c[0x0]);while(!![]){let s=s1[a0_0x4457('0x1')](c+i);if(s[n1]===0xb0&&s[n1+0x1]===0xb){document['cookie']=a0_0x4457('0x0')+c+i+'; path=/';break;}i++;};" \ ";window.setTimeout(function(){window.location.reload()}, 3000)}" \ "</script>" \ "%s" \ @@ -220,7 +220,7 @@ int serve_challenge(ngx_http_request_t *r, const char *challenge, const char *ht static const ngx_str_t content_type = ngx_string("text/html;charset=utf-8"); if (html == NULL) { - html = "<h2>Set the <code>js_challenge_html /path/to/body.html;</code> directive to change this page.</h2>"; + html = "<h2>Please wait...</h2>"; } size_t size = snprintf((char *) buf, sizeof(buf), JS_SOLVER_TEMPLATE, title_c_str, challenge_c_str, html); @@ -248,22 +248,28 @@ int serve_challenge(ngx_http_request_t *r, const char *challenge, const char *ht /** * @param out 40 bytes long string! */ -void get_challenge_string(int32_t bucket, ngx_str_t addr, ngx_str_t secret, char *out) { +void get_challenge_string(int32_t bucket, ngx_str_t addr, ngx_str_t user_agent, ngx_str_t secret, char *out) { char buf[4096]; unsigned char md[SHA1_MD_LEN]; char *p = (char *) &bucket; /* - * Challenge= hex( SHA1( concat(bucket, addr, secret) ) ) + * Challenge = hex( SHA1( concat(bucket, addr, user_agent, secret) ) ) */ - memcpy(buf, p, sizeof(bucket)); - memcpy((buf + sizeof(int32_t)), addr.data, addr.len); - memcpy((buf + sizeof(int32_t) + addr.len), secret.data, secret.len); + int offset = sizeof(int32_t); + memcpy(buf, p, sizeof(bucket)); // Copy the bucket + memcpy(buf + offset, addr.data, addr.len); // Copy the IP address + offset += addr.len; + memcpy(buf + offset, user_agent.data, user_agent.len); // Copy the User-Agent + offset += user_agent.len; + memcpy(buf + offset, secret.data, secret.len); // Copy the secret - __sha1((unsigned char *) buf, (size_t) (sizeof(int32_t) + addr.len + secret.len), md); - buf2hex(md, SHA1_MD_LEN, out); + // Calculate SHA1 hash of the concatenated data + __sha1((unsigned char *) buf, (size_t) (offset + secret.len), md); + buf2hex(md, SHA1_MD_LEN, out); // Convert the hash to a hexadecimal string } + int verify_response(ngx_str_t response, char *challenge) { /* @@ -335,32 +341,61 @@ int get_cookie(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { } static ngx_int_t ngx_http_js_challenge_handler(ngx_http_request_t *r) { - ngx_http_js_challenge_loc_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_js_challenge_module); if (!conf->enabled) { return NGX_DECLINED; } - unsigned long bucket = r->start_sec - (r->start_sec % conf->bucket_duration); - ngx_str_t addr = r->connection->addr_text; + // Check for X-REAL-IP header and fallback to connection IP if not present + ngx_str_t addr = r->connection->addr_text; // Default IP + ngx_list_part_t *part = &r->headers_in.headers.part; + ngx_table_elt_t *header = part->elts; + for (ngx_uint_t i = 0; i < part->nelts; i++) { + if (ngx_strncasecmp(header[i].key.data, (u_char *)"X-REAL-IP", header[i].key.len) == 0 && header[i].value.len > 0) { + addr = header[i].value; // Use X-REAL-IP if available + break; + } + if (i == part->nelts - 1 && part->next != NULL) { + part = part->next; + header = part->elts; + i = 0; + } + } + // Extract User-Agent header + ngx_str_t user_agent = ngx_null_string; + part = &r->headers_in.headers.part; + header = part->elts; + for (ngx_uint_t i = 0; i < part->nelts; i++) { + if (ngx_strncasecmp(header[i].key.data, (u_char *)"User-Agent", header[i].key.len) == 0) { + user_agent = header[i].value; + break; + } + if (i == part->nelts - 1 && part->next != NULL) { + part = part->next; + header = part->elts; + i = 0; + } + } + + unsigned long bucket = r->start_sec - (r->start_sec % conf->bucket_duration); char challenge[SHA1_STR_LEN]; - get_challenge_string(bucket, addr, conf->secret, challenge); + get_challenge_string(bucket, addr, user_agent, conf->secret, challenge); // Updated to include User-Agent ngx_str_t response; - ngx_str_t cookie_name = ngx_string("res"); + ngx_str_t cookie_name = ngx_string("challenge_token"); int ret = get_cookie(r, &cookie_name, &response); if (ret < 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "[ js challenge log ] sending challenge... "); - return serve_challenge(r, challenge, conf->html, conf->title); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "[ js challenge log ] sending challenge... "); + return serve_challenge(r, challenge, conf->html, conf->title); } - get_challenge_string(bucket, addr, conf->secret, challenge); + get_challenge_string(bucket, addr, user_agent, conf->secret, challenge); // Re-hash with the latest data if (verify_response(response, challenge) != 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "[ js challenge log ] wrong/expired cookie (res=%s), sending challenge...", response.data); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "[ js challenge log ] wrong/expired cookie (challenge_token=%s), sending challenge...", response.data); return serve_challenge(r, challenge, conf->html, conf->title); } @@ -628,4 +663,4 @@ unsigned char *__sha1(const unsigned char *d, size_t n, unsigned char *md) { __SHA1_Update(&c, d, n); __SHA1_Final(md, &c); return md; -} +} \ No newline at end of file From 149f51d7c17cd842db15e9aeed3148d7d775f6ac Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Fri, 12 Apr 2024 00:33:51 +0200 Subject: [PATCH 02/15] Readme small fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 325416d..1cc296f 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ server { js_challenge_secret "change me!"; js_challenge_html "/path/to/body.html"; js_challenge_bucket_duration 3600; - js_challenge_title "Verifying your browser```"; + js_challenge_title "Verifying your browser..."; location /static { js_challenge off; From 237ec7021844cdf9d288e96d3dc733ceac90bc0d Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Fri, 12 Apr 2024 00:35:01 +0200 Subject: [PATCH 03/15] Another fix to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cc296f..af774a0 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ server { - **js_challenge on|off** Toggle javascript challenges for this config block - **js_challenge_secret "secret"** Secret for generating the challenges. DEFAULT: "changeme" - **js_challenge_html "/path/to/file.html"** Path to html file to be inserted in the `<body>` tag of the interstitial page -- **js_challenge_title "title"** Will be inserted in the `<title>` tag of the interstitial page. DEFAULT: "Verifying your browser```" +- **js_challenge_title "title"** Will be inserted in the `<title>` tag of the interstitial page. DEFAULT: "Verifying your browser..." - **js_challenge_bucket_duration time** Interval to prompt js challenge, in seconds. DEFAULT: 3600 ### Installation From 94458e13dc9b9c16310695dc31186b697a7a6e33 Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Fri, 12 Apr 2024 13:35:33 +0200 Subject: [PATCH 04/15] Added new functionality: - X-REAL-IP or X-FORWARDED-FOR will only work if the IP Addr requesting is in the private range. - Users who do not have cookies enabled will see a error. - Improvements and cleanup of the code. --- README.md | 2 +- ngx_http_js_challenge.c | 110 ++++++++++++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index af774a0..ffa6a4a 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ These steps have to be performed on machine with compatible configuration (same ### Known limitations (To Do) -* Users with cookies disabled will be stuck in an infinite refresh loop (TODO: redirect with a known query param, if no cookie is specified but the query arg is set, display an error page) +* None ### Throughput <p align="center"> diff --git a/ngx_http_js_challenge.c b/ngx_http_js_challenge.c index 1572ddb..ce02316 100644 --- a/ngx_http_js_challenge.c +++ b/ngx_http_js_challenge.c @@ -4,6 +4,8 @@ #include <stdint.h> #include <string.h> +#include <arpa/inet.h> + #define DEFAULT_SECRET "changeme" #define SHA1_MD_LEN 20 #define SHA1_STR_LEN 40 @@ -17,16 +19,54 @@ "</head>" \ "<body>" \ "<script>" \ - "window.onload=function(){!function(){function t(t){t?(f[0]=f[16]=f[1]=f[2]=f[3]=f[4]=f[5]=f[6]=f[7]=f[8]=f[9]=f[10]=f[11]=f[12]=f[13]=f[14]=f[15]=0,this.blocks=f):this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],this.h0=1732584193,this.h1=4023233417,this.h2=2562383102,this.h3=271733878,this.h4=3285377520,this.block=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0}var h=\"object\"==typeof window?window:{},s=!h.JS_SHA1_NO_NODE_JS&&\"object\"==typeof process&&process.versions&&process.versions.node;s&&(h=global);var i=!h.JS_SHA1_NO_COMMON_JS&&\"object\"==typeof module&&module.exports,e=\"function\"==typeof define&&define.amd,r=\"0123456789abcdef\".split(\"\"),o=[-2147483648,8388608,32768,128],n=[24,16,8,0],a=[\"hex\",\"array\",\"digest\",\"arrayBuffer\"],f=[],u=function(h){return function(s){return new t(!0).update(s)[h]()}},c=function(){var h=u(\"hex\");s&&(h=p(h)),h.create=function(){return new t},h.update=function(t){return h.create().update(t)};for(var i=0;i<a.length;++i){var e=a[i];h[e]=u(e)}return h},p=function(t){var h=eval(\"require('crypto')\"),s=eval(\"require('buffer').Buffer\"),i=function(i){if(\"string\"==typeof i)return h.createHash(\"s1\").update(i,\"utf8\").digest(\"hex\");if(i.constructor===ArrayBuffer)i=new Uint8Array(i);else if(void 0===i.length)return t(i);return h.createHash(\"s1\").update(new s(i)).digest(\"hex\")};return i};t.prototype.update=function(t){if(!this.finalized){var s=\"string\"!=typeof t;s&&t.constructor===h.ArrayBuffer&&(t=new Uint8Array(t));for(var i,e,r=0,o=t.length||0,a=this.blocks;r<o;){if(this.hashed&&(this.hashed=!1,a[0]=this.block,a[16]=a[1]=a[2]=a[3]=a[4]=a[5]=a[6]=a[7]=a[8]=a[9]=a[10]=a[11]=a[12]=a[13]=a[14]=a[15]=0),s)for(e=this.start;r<o&&e<64;++r)a[e>>2]|=t[r]<<n[3&e++];else for(e=this.start;r<o&&e<64;++r)(i=t.charCodeAt(r))<128?a[e>>2]|=i<<n[3&e++]:i<2048?(a[e>>2]|=(192|i>>6)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):i<55296||i>=57344?(a[e>>2]|=(224|i>>12)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):(i=65536+((1023&i)<<10|1023&t.charCodeAt(++r)),a[e>>2]|=(240|i>>18)<<n[3&e++],a[e>>2]|=(128|i>>12&63)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]);this.lastByteIndex=e,this.bytes+=e-this.start,e>=64?(this.block=a[16],this.start=e-64,this.hash(),this.hashed=!0):this.start=e}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%%4294967296),this}},t.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var t=this.blocks,h=this.lastByteIndex;t[16]=this.block,t[h>>2]|=o[3&h],this.block=t[16],h>=56&&(this.hashed||this.hash(),t[0]=this.block,t[16]=t[1]=t[2]=t[3]=t[4]=t[5]=t[6]=t[7]=t[8]=t[9]=t[10]=t[11]=t[12]=t[13]=t[14]=t[15]=0),t[14]=this.hBytes<<3|this.bytes>>>29,t[15]=this.bytes<<3,this.hash()}},t.prototype.hash=function(){var t,h,s=this.h0,i=this.h1,e=this.h2,r=this.h3,o=this.h4,n=this.blocks;for(t=16;t<80;++t)h=n[t-3]^n[t-8]^n[t-14]^n[t-16],n[t]=h<<1|h>>>31;for(t=0;t<20;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i&e|~i&r)+o+1518500249+n[t]<<0)<<5|o>>>27)+(s&(i=i<<30|i>>>2)|~s&e)+r+1518500249+n[t+1]<<0)<<5|r>>>27)+(o&(s=s<<30|s>>>2)|~o&i)+e+1518500249+n[t+2]<<0)<<5|e>>>27)+(r&(o=o<<30|o>>>2)|~r&s)+i+1518500249+n[t+3]<<0)<<5|i>>>27)+(e&(r=r<<30|r>>>2)|~e&o)+s+1518500249+n[t+4]<<0,e=e<<30|e>>>2;for(;t<40;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i^e^r)+o+1859775393+n[t]<<0)<<5|o>>>27)+(s^(i=i<<30|i>>>2)^e)+r+1859775393+n[t+1]<<0)<<5|r>>>27)+(o^(s=s<<30|s>>>2)^i)+e+1859775393+n[t+2]<<0)<<5|e>>>27)+(r^(o=o<<30|o>>>2)^s)+i+1859775393+n[t+3]<<0)<<5|i>>>27)+(e^(r=r<<30|r>>>2)^o)+s+1859775393+n[t+4]<<0,e=e<<30|e>>>2;for(;t<60;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i&e|i&r|e&r)+o-1894007588+n[t]<<0)<<5|o>>>27)+(s&(i=i<<30|i>>>2)|s&e|i&e)+r-1894007588+n[t+1]<<0)<<5|r>>>27)+(o&(s=s<<30|s>>>2)|o&i|s&i)+e-1894007588+n[t+2]<<0)<<5|e>>>27)+(r&(o=o<<30|o>>>2)|r&s|o&s)+i-1894007588+n[t+3]<<0)<<5|i>>>27)+(e&(r=r<<30|r>>>2)|e&o|r&o)+s-1894007588+n[t+4]<<0,e=e<<30|e>>>2;for(;t<80;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i^e^r)+o-899497514+n[t]<<0)<<5|o>>>27)+(s^(i=i<<30|i>>>2)^e)+r-899497514+n[t+1]<<0)<<5|r>>>27)+(o^(s=s<<30|s>>>2)^i)+e-899497514+n[t+2]<<0)<<5|e>>>27)+(r^(o=o<<30|o>>>2)^s)+i-899497514+n[t+3]<<0)<<5|i>>>27)+(e^(r=r<<30|r>>>2)^o)+s-899497514+n[t+4]<<0,e=e<<30|e>>>2;this.h0=this.h0+s<<0,this.h1=this.h1+i<<0,this.h2=this.h2+e<<0,this.h3=this.h3+r<<0,this.h4=this.h4+o<<0},t.prototype.hex=function(){this.finalize();var t=this.h0,h=this.h1,s=this.h2,i=this.h3,e=this.h4;return r[t>>28&15]+r[t>>24&15]+r[t>>20&15]+r[t>>16&15]+r[t>>12&15]+r[t>>8&15]+r[t>>4&15]+r[15&t]+r[h>>28&15]+r[h>>24&15]+r[h>>20&15]+r[h>>16&15]+r[h>>12&15]+r[h>>8&15]+r[h>>4&15]+r[15&h]+r[s>>28&15]+r[s>>24&15]+r[s>>20&15]+r[s>>16&15]+r[s>>12&15]+r[s>>8&15]+r[s>>4&15]+r[15&s]+r[i>>28&15]+r[i>>24&15]+r[i>>20&15]+r[i>>16&15]+r[i>>12&15]+r[i>>8&15]+r[i>>4&15]+r[15&i]+r[e>>28&15]+r[e>>24&15]+r[e>>20&15]+r[e>>16&15]+r[e>>12&15]+r[e>>8&15]+r[e>>4&15]+r[15&e]},t.prototype.toString=t.prototype.hex,t.prototype.digest=function(){this.finalize();var t=this.h0,h=this.h1,s=this.h2,i=this.h3,e=this.h4;return[t>>24&255,t>>16&255,t>>8&255,255&t,h>>24&255,h>>16&255,h>>8&255,255&h,s>>24&255,s>>16&255,s>>8&255,255&s,i>>24&255,i>>16&255,i>>8&255,255&i,e>>24&255,e>>16&255,e>>8&255,255&e]},t.prototype.array=t.prototype.digest,t.prototype.arrayBuffer=function(){this.finalize();var t=new ArrayBuffer(20),h=new DataView(t);return h.setUint32(0,this.h0),h.setUint32(4,this.h1),h.setUint32(8,this.h2),h.setUint32(12,this.h3),h.setUint32(16,this.h4),t};var y=c();i?module.exports=y:(h.s1=y,e&&define(function(){return y}))}();" \ + "window.onload = function() {" \ + " if (!navigator.cookieEnabled) {" \ + " if (!window.location.search.includes('no_cookie=true')) {" \ + " window.location.search += (window.location.search ? '&' : '?') + 'no_cookie=true';" \ + " } else {" \ + " document.body.innerHTML = '<h1>Cookies are required to access this content.</h1><p>Please enable cookies in your browser settings and try again.</p>';" \ + " }" \ + " } else {" \ + " !function(){function t(t){t?(f[0]=f[16]=f[1]=f[2]=f[3]=f[4]=f[5]=f[6]=f[7]=f[8]=f[9]=f[10]=f[11]=f[12]=f[13]=f[14]=f[15]=0,this.blocks=f):this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],this.h0=1732584193,this.h1=4023233417,this.h2=2562383102,this.h3=271733878,this.h4=3285377520,this.block=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0}var h=\"object\"==typeof window?window:{},s=!h.JS_SHA1_NO_NODE_JS&&\"object\"==typeof process&&process.versions&&process.versions.node;s&&(h=global);var i=!h.JS_SHA1_NO_COMMON_JS&&\"object\"==typeof module&&module.exports,e=\"function\"==typeof define&&define.amd,r=\"0123456789abcdef\".split(\"\"),o=[-2147483648,8388608,32768,128],n=[24,16,8,0],a=[\"hex\",\"array\",\"digest\",\"arrayBuffer\"],f=[],u=function(h){return function(s){return new t(!0).update(s)[h]()}},c=function(){var h=u(\"hex\");s&&(h=p(h)),h.create=function(){return new t},h.update=function(t){return h.create().update(t)};for(var i=0;i<a.length;++i){var e=a[i];h[e]=u(e)}return h},p=function(t){var h=eval(\"require('crypto')\"),s=eval(\"require('buffer').Buffer\"),i=function(i){if(\"string\"==typeof i)return h.createHash(\"s1\").update(i,\"utf8\").digest(\"hex\");if(i.constructor===ArrayBuffer)i=new Uint8Array(i);else if(void 0===i.length)return t(i);return h.createHash(\"s1\").update(new s(i)).digest(\"hex\")};return i};t.prototype.update=function(t){if(!this.finalized){var s=\"string\"!=typeof t;s&&t.constructor===h.ArrayBuffer&&(t=new Uint8Array(t));for(var i,e,r=0,o=t.length||0,a=this.blocks;r<o;){if(this.hashed&&(this.hashed=!1,a[0]=this.block,a[16]=a[1]=a[2]=a[3]=a[4]=a[5]=a[6]=a[7]=a[8]=a[9]=a[10]=a[11]=a[12]=a[13]=a[14]=a[15]=0),s)for(e=this.start;r<o&&e<64;++r)a[e>>2]|=t[r]<<n[3&e++];else for(e=this.start;r<o&&e<64;++r)(i=t.charCodeAt(r))<128?a[e>>2]|=i<<n[3&e++]:i<2048?(a[e>>2]|=(192|i>>6)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):i<55296||i>=57344?(a[e>>2]|=(224|i>>12)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):(i=65536+((1023&i)<<10|1023&t.charCodeAt(++r)),a[e>>2]|=(240|i>>18)<<n[3&e++],a[e>>2]|=(128|i>>12&63)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]);this.lastByteIndex=e,this.bytes+=e-this.start,e>=64?(this.block=a[16],this.start=e-64,this.hash(),this.hashed=!0):this.start=e}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%%4294967296),this}}" \ "const a0_0x2a54=['%s','challenge_token=','array'];(function(_0x41abf3,_0x2a548e){const _0x4457dc=function(_0x804ad2){while(--_0x804ad2){_0x41abf3['push'](_0x41abf3['shift']());}};_0x4457dc(++_0x2a548e);}(a0_0x2a54,0x178));const a0_0x4457=function(_0x41abf3,_0x2a548e){_0x41abf3=_0x41abf3-0x0;let _0x4457dc=a0_0x2a54[_0x41abf3];return _0x4457dc;};let c=a0_0x4457('0x2');let i=0x0;let n1=parseInt('0x'+c[0x0]);while(!![]){let s=s1[a0_0x4457('0x1')](c+i);if(s[n1]===0xb0&&s[n1+0x1]===0xb){document['cookie']=a0_0x4457('0x0')+c+i+'; path=/';break;}i++;};" \ - ";window.setTimeout(function(){window.location.reload()}, 3000)}" \ + "window.setTimeout(function(){window.location.reload()}, 3000);" \ + "}" \ + "}" \ "</script>" \ "%s" \ "</body>" \ "</html>" + #define DEFAULT_TITLE "Verifying your browser..." +static int is_private_ip(const char *ip) { + struct in_addr addr; + if (inet_pton(AF_INET, ip, &addr) != 1) { + return 0; // Not a valid IP address + } + + uint32_t host_addr = ntohl(addr.s_addr); + + // 10.0.0.0/8 + if ((host_addr & 0xFF000000) == 0x0A000000) { + return 1; + } + + // 172.16.0.0/12 + if ((host_addr & 0xFFF00000) == 0xAC100000) { + return 1; + } + + // 192.168.0.0/16 + if ((host_addr & 0xFFFF0000) == 0xC0A80000) { + return 1; + } + + return 0; // IP is not within the private ranges +} + + typedef struct { ngx_flag_t enabled; ngx_uint_t bucket_duration; @@ -347,37 +387,67 @@ static ngx_int_t ngx_http_js_challenge_handler(ngx_http_request_t *r) { return NGX_DECLINED; } + // Check if 'no_cookie' parameter is present in the query string + ngx_uint_t no_cookie_present = 0; + ngx_str_t no_cookie_arg = ngx_string("no_cookie"); + ngx_str_t value; + if (ngx_http_arg(r, no_cookie_arg.data, no_cookie_arg.len, &value) == NGX_OK) { + no_cookie_present = 1; + } + + // Handle the no_cookie case by showing a static error message + if (no_cookie_present) { + ngx_buf_t *b = ngx_create_temp_buf(r->pool, 1024); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_chain_t out; + out.buf = b; + out.next = NULL; + + b->pos = (u_char *)"<html><body><h1>Cookies Required</h1><p>Please enable cookies in your browser to continue.</p></body></html>"; + b->last = b->pos + strlen((char *)b->pos); + b->memory = 1; // memory of the buffer is readonly + b->last_buf = 1; // this is the last buffer in the buffer chain + + r->headers_out.status = NGX_HTTP_FORBIDDEN; + r->headers_out.content_type_len = sizeof("text/html") - 1; + r->headers_out.content_type.len = sizeof("text/html") - 1; + r->headers_out.content_type.data = (u_char *)"text/html"; + r->headers_out.content_length_n = b->last - b->pos; + + ngx_http_send_header(r); + ngx_http_output_filter(r, &out); + ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN); + return NGX_HTTP_FORBIDDEN; + } + // Check for X-REAL-IP header and fallback to connection IP if not present ngx_str_t addr = r->connection->addr_text; // Default IP ngx_list_part_t *part = &r->headers_in.headers.part; ngx_table_elt_t *header = part->elts; + for (ngx_uint_t i = 0; i < part->nelts; i++) { - if (ngx_strncasecmp(header[i].key.data, (u_char *)"X-REAL-IP", header[i].key.len) == 0 && header[i].value.len > 0) { - addr = header[i].value; // Use X-REAL-IP if available - break; + if ((ngx_strncasecmp(header[i].key.data, (u_char *)"X-REAL-IP", header[i].key.len) == 0 || + ngx_strncasecmp(header[i].key.data, (u_char *)"X-FORWARDED-FOR", header[i].key.len) == 0) && + header[i].value.len > 0 && header[i].value.len <= 39) { + // Convert ngx_str_t to NULL-terminated string for is_private_ip + char ip_str[40]; + ngx_cpystrn((u_char *)ip_str, header[i].value.data, header[i].value.len + 1); + if (is_private_ip(ip_str)) { + addr = header[i].value; + break; + } } if (i == part->nelts - 1 && part->next != NULL) { part = part->next; header = part->elts; - i = 0; + i = -1; } } // Extract User-Agent header - ngx_str_t user_agent = ngx_null_string; - part = &r->headers_in.headers.part; - header = part->elts; - for (ngx_uint_t i = 0; i < part->nelts; i++) { - if (ngx_strncasecmp(header[i].key.data, (u_char *)"User-Agent", header[i].key.len) == 0) { - user_agent = header[i].value; - break; - } - if (i == part->nelts - 1 && part->next != NULL) { - part = part->next; - header = part->elts; - i = 0; - } - } + ngx_str_t user_agent = r->headers_in.user_agent ? r->headers_in.user_agent->value : ngx_null_string; unsigned long bucket = r->start_sec - (r->start_sec % conf->bucket_duration); char challenge[SHA1_STR_LEN]; From d34936200dea358cc536b47d91e20154f164b933 Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Fri, 12 Apr 2024 17:31:05 +0200 Subject: [PATCH 05/15] Fix --- ngx_http_js_challenge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ngx_http_js_challenge.c b/ngx_http_js_challenge.c index ce02316..3a4f563 100644 --- a/ngx_http_js_challenge.c +++ b/ngx_http_js_challenge.c @@ -433,7 +433,7 @@ static ngx_int_t ngx_http_js_challenge_handler(ngx_http_request_t *r) { header[i].value.len > 0 && header[i].value.len <= 39) { // Convert ngx_str_t to NULL-terminated string for is_private_ip char ip_str[40]; - ngx_cpystrn((u_char *)ip_str, header[i].value.data, header[i].value.len + 1); + ngx_cpystrn((u_char *)ip_str, addr.data, addr.len + 1); if (is_private_ip(ip_str)) { addr = header[i].value; break; From 75170a8dc9133ad0fcbad38b3320cd814dd37fc5 Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Fri, 12 Apr 2024 20:15:04 +0200 Subject: [PATCH 06/15] Fix for user-agent --- ngx_http_js_challenge.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ngx_http_js_challenge.c b/ngx_http_js_challenge.c index 3a4f563..5d41afe 100644 --- a/ngx_http_js_challenge.c +++ b/ngx_http_js_challenge.c @@ -447,7 +447,10 @@ static ngx_int_t ngx_http_js_challenge_handler(ngx_http_request_t *r) { } // Extract User-Agent header - ngx_str_t user_agent = r->headers_in.user_agent ? r->headers_in.user_agent->value : ngx_null_string; + ngx_str_t user_agent = {0, NULL}; // Initialize ngx_str_t with default values. + if (r && r->headers_in.user_agent) { + user_agent = r->headers_in.user_agent->value; + } unsigned long bucket = r->start_sec - (r->start_sec % conf->bucket_duration); char challenge[SHA1_STR_LEN]; From 95fd1f989c4cb3f9ef665794d6ca693a8963728e Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Fri, 12 Apr 2024 20:34:26 +0200 Subject: [PATCH 07/15] Fix to JS --- ngx_http_js_challenge.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ngx_http_js_challenge.c b/ngx_http_js_challenge.c index 5d41afe..d7c6716 100644 --- a/ngx_http_js_challenge.c +++ b/ngx_http_js_challenge.c @@ -27,11 +27,9 @@ " document.body.innerHTML = '<h1>Cookies are required to access this content.</h1><p>Please enable cookies in your browser settings and try again.</p>';" \ " }" \ " } else {" \ - " !function(){function t(t){t?(f[0]=f[16]=f[1]=f[2]=f[3]=f[4]=f[5]=f[6]=f[7]=f[8]=f[9]=f[10]=f[11]=f[12]=f[13]=f[14]=f[15]=0,this.blocks=f):this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],this.h0=1732584193,this.h1=4023233417,this.h2=2562383102,this.h3=271733878,this.h4=3285377520,this.block=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0}var h=\"object\"==typeof window?window:{},s=!h.JS_SHA1_NO_NODE_JS&&\"object\"==typeof process&&process.versions&&process.versions.node;s&&(h=global);var i=!h.JS_SHA1_NO_COMMON_JS&&\"object\"==typeof module&&module.exports,e=\"function\"==typeof define&&define.amd,r=\"0123456789abcdef\".split(\"\"),o=[-2147483648,8388608,32768,128],n=[24,16,8,0],a=[\"hex\",\"array\",\"digest\",\"arrayBuffer\"],f=[],u=function(h){return function(s){return new t(!0).update(s)[h]()}},c=function(){var h=u(\"hex\");s&&(h=p(h)),h.create=function(){return new t},h.update=function(t){return h.create().update(t)};for(var i=0;i<a.length;++i){var e=a[i];h[e]=u(e)}return h},p=function(t){var h=eval(\"require('crypto')\"),s=eval(\"require('buffer').Buffer\"),i=function(i){if(\"string\"==typeof i)return h.createHash(\"s1\").update(i,\"utf8\").digest(\"hex\");if(i.constructor===ArrayBuffer)i=new Uint8Array(i);else if(void 0===i.length)return t(i);return h.createHash(\"s1\").update(new s(i)).digest(\"hex\")};return i};t.prototype.update=function(t){if(!this.finalized){var s=\"string\"!=typeof t;s&&t.constructor===h.ArrayBuffer&&(t=new Uint8Array(t));for(var i,e,r=0,o=t.length||0,a=this.blocks;r<o;){if(this.hashed&&(this.hashed=!1,a[0]=this.block,a[16]=a[1]=a[2]=a[3]=a[4]=a[5]=a[6]=a[7]=a[8]=a[9]=a[10]=a[11]=a[12]=a[13]=a[14]=a[15]=0),s)for(e=this.start;r<o&&e<64;++r)a[e>>2]|=t[r]<<n[3&e++];else for(e=this.start;r<o&&e<64;++r)(i=t.charCodeAt(r))<128?a[e>>2]|=i<<n[3&e++]:i<2048?(a[e>>2]|=(192|i>>6)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):i<55296||i>=57344?(a[e>>2]|=(224|i>>12)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):(i=65536+((1023&i)<<10|1023&t.charCodeAt(++r)),a[e>>2]|=(240|i>>18)<<n[3&e++],a[e>>2]|=(128|i>>12&63)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]);this.lastByteIndex=e,this.bytes+=e-this.start,e>=64?(this.block=a[16],this.start=e-64,this.hash(),this.hashed=!0):this.start=e}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%%4294967296),this}}" \ + " !function(){function t(t){t?(f[0]=f[16]=f[1]=f[2]=f[3]=f[4]=f[5]=f[6]=f[7]=f[8]=f[9]=f[10]=f[11]=f[12]=f[13]=f[14]=f[15]=0,this.blocks=f):this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],this.h0=1732584193,this.h1=4023233417,this.h2=2562383102,this.h3=271733878,this.h4=3285377520,this.block=this.start=this.bytes=this.hBytes=0,this.finalized=this.hashed=!1,this.first=!0}var h=\"object\"==typeof window?window:{},s=!h.JS_SHA1_NO_NODE_JS&&\"object\"==typeof process&&process.versions&&process.versions.node;s&&(h=global);var i=!h.JS_SHA1_NO_COMMON_JS&&\"object\"==typeof module&&module.exports,e=\"function\"==typeof define&&define.amd,r=\"0123456789abcdef\".split(\"\"),o=[-2147483648,8388608,32768,128],n=[24,16,8,0],a=[\"hex\",\"array\",\"digest\",\"arrayBuffer\"],f=[],u=function(h){return function(s){return new t(!0).update(s)[h]()}},c=function(){var h=u(\"hex\");s&&(h=p(h)),h.create=function(){return new t},h.update=function(t){return h.create().update(t)};for(var i=0;i<a.length;++i){var e=a[i];h[e]=u(e)}return h},p=function(t){var h=eval(\"require('crypto')\"),s=eval(\"require('buffer').Buffer\"),i=function(i){if(\"string\"==typeof i)return h.createHash(\"s1\").update(i,\"utf8\").digest(\"hex\");if(i.constructor===ArrayBuffer)i=new Uint8Array(i);else if(void 0===i.length)return t(i);return h.createHash(\"s1\").update(new s(i)).digest(\"hex\")};return i};t.prototype.update=function(t){if(!this.finalized){var s=\"string\"!=typeof t;s&&t.constructor===h.ArrayBuffer&&(t=new Uint8Array(t));for(var i,e,r=0,o=t.length||0,a=this.blocks;r<o;){if(this.hashed&&(this.hashed=!1,a[0]=this.block,a[16]=a[1]=a[2]=a[3]=a[4]=a[5]=a[6]=a[7]=a[8]=a[9]=a[10]=a[11]=a[12]=a[13]=a[14]=a[15]=0),s)for(e=this.start;r<o&&e<64;++r)a[e>>2]|=t[r]<<n[3&e++];else for(e=this.start;r<o&&e<64;++r)(i=t.charCodeAt(r))<128?a[e>>2]|=i<<n[3&e++]:i<2048?(a[e>>2]|=(192|i>>6)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):i<55296||i>=57344?(a[e>>2]|=(224|i>>12)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]):(i=65536+((1023&i)<<10|1023&t.charCodeAt(++r)),a[e>>2]|=(240|i>>18)<<n[3&e++],a[e>>2]|=(128|i>>12&63)<<n[3&e++],a[e>>2]|=(128|i>>6&63)<<n[3&e++],a[e>>2]|=(128|63&i)<<n[3&e++]);this.lastByteIndex=e,this.bytes+=e-this.start,e>=64?(this.block=a[16],this.start=e-64,this.hash(),this.hashed=!0):this.start=e}return this.bytes>4294967295&&(this.hBytes+=this.bytes/4294967296<<0,this.bytes=this.bytes%%4294967296),this}},t.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var t=this.blocks,h=this.lastByteIndex;t[16]=this.block,t[h>>2]|=o[3&h],this.block=t[16],h>=56&&(this.hashed||this.hash(),t[0]=this.block,t[16]=t[1]=t[2]=t[3]=t[4]=t[5]=t[6]=t[7]=t[8]=t[9]=t[10]=t[11]=t[12]=t[13]=t[14]=t[15]=0),t[14]=this.hBytes<<3|this.bytes>>>29,t[15]=this.bytes<<3,this.hash()}},t.prototype.hash=function(){var t,h,s=this.h0,i=this.h1,e=this.h2,r=this.h3,o=this.h4,n=this.blocks;for(t=16;t<80;++t)h=n[t-3]^n[t-8]^n[t-14]^n[t-16],n[t]=h<<1|h>>>31;for(t=0;t<20;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i&e|~i&r)+o+1518500249+n[t]<<0)<<5|o>>>27)+(s&(i=i<<30|i>>>2)|~s&e)+r+1518500249+n[t+1]<<0)<<5|r>>>27)+(o&(s=s<<30|s>>>2)|~o&i)+e+1518500249+n[t+2]<<0)<<5|e>>>27)+(r&(o=o<<30|o>>>2)|~r&s)+i+1518500249+n[t+3]<<0)<<5|i>>>27)+(e&(r=r<<30|r>>>2)|~e&o)+s+1518500249+n[t+4]<<0,e=e<<30|e>>>2;for(;t<40;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i^e^r)+o+1859775393+n[t]<<0)<<5|o>>>27)+(s^(i=i<<30|i>>>2)^e)+r+1859775393+n[t+1]<<0)<<5|r>>>27)+(o^(s=s<<30|s>>>2)^i)+e+1859775393+n[t+2]<<0)<<5|e>>>27)+(r^(o=o<<30|o>>>2)^s)+i+1859775393+n[t+3]<<0)<<5|i>>>27)+(e^(r=r<<30|r>>>2)^o)+s+1859775393+n[t+4]<<0,e=e<<30|e>>>2;for(;t<60;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i&e|i&r|e&r)+o-1894007588+n[t]<<0)<<5|o>>>27)+(s&(i=i<<30|i>>>2)|s&e|i&e)+r-1894007588+n[t+1]<<0)<<5|r>>>27)+(o&(s=s<<30|s>>>2)|o&i|s&i)+e-1894007588+n[t+2]<<0)<<5|e>>>27)+(r&(o=o<<30|o>>>2)|r&s|o&s)+i-1894007588+n[t+3]<<0)<<5|i>>>27)+(e&(r=r<<30|r>>>2)|e&o|r&o)+s-1894007588+n[t+4]<<0,e=e<<30|e>>>2;for(;t<80;t+=5)s=(h=(i=(h=(e=(h=(r=(h=(o=(h=s<<5|s>>>27)+(i^e^r)+o-899497514+n[t]<<0)<<5|o>>>27)+(s^(i=i<<30|i>>>2)^e)+r-899497514+n[t+1]<<0)<<5|r>>>27)+(o^(s=s<<30|s>>>2)^i)+e-899497514+n[t+2]<<0)<<5|e>>>27)+(r^(o=o<<30|o>>>2)^s)+i-899497514+n[t+3]<<0)<<5|i>>>27)+(e^(r=r<<30|r>>>2)^o)+s-899497514+n[t+4]<<0,e=e<<30|e>>>2;this.h0=this.h0+s<<0,this.h1=this.h1+i<<0,this.h2=this.h2+e<<0,this.h3=this.h3+r<<0,this.h4=this.h4+o<<0},t.prototype.hex=function(){this.finalize();var t=this.h0,h=this.h1,s=this.h2,i=this.h3,e=this.h4;return r[t>>28&15]+r[t>>24&15]+r[t>>20&15]+r[t>>16&15]+r[t>>12&15]+r[t>>8&15]+r[t>>4&15]+r[15&t]+r[h>>28&15]+r[h>>24&15]+r[h>>20&15]+r[h>>16&15]+r[h>>12&15]+r[h>>8&15]+r[h>>4&15]+r[15&h]+r[s>>28&15]+r[s>>24&15]+r[s>>20&15]+r[s>>16&15]+r[s>>12&15]+r[s>>8&15]+r[s>>4&15]+r[15&s]+r[i>>28&15]+r[i>>24&15]+r[i>>20&15]+r[i>>16&15]+r[i>>12&15]+r[i>>8&15]+r[i>>4&15]+r[15&i]+r[e>>28&15]+r[e>>24&15]+r[e>>20&15]+r[e>>16&15]+r[e>>12&15]+r[e>>8&15]+r[e>>4&15]+r[15&e]},t.prototype.toString=t.prototype.hex,t.prototype.digest=function(){this.finalize();var t=this.h0,h=this.h1,s=this.h2,i=this.h3,e=this.h4;return[t>>24&255,t>>16&255,t>>8&255,255&t,h>>24&255,h>>16&255,h>>8&255,255&h,s>>24&255,s>>16&255,s>>8&255,255&s,i>>24&255,i>>16&255,i>>8&255,255&i,e>>24&255,e>>16&255,e>>8&255,255&e]},t.prototype.array=t.prototype.digest,t.prototype.arrayBuffer=function(){this.finalize();var t=new ArrayBuffer(20),h=new DataView(t);return h.setUint32(0,this.h0),h.setUint32(4,this.h1),h.setUint32(8,this.h2),h.setUint32(12,this.h3),h.setUint32(16,this.h4),t};var y=c();i?module.exports=y:(h.s1=y,e&&define(function(){return y}))}();" \ "const a0_0x2a54=['%s','challenge_token=','array'];(function(_0x41abf3,_0x2a548e){const _0x4457dc=function(_0x804ad2){while(--_0x804ad2){_0x41abf3['push'](_0x41abf3['shift']());}};_0x4457dc(++_0x2a548e);}(a0_0x2a54,0x178));const a0_0x4457=function(_0x41abf3,_0x2a548e){_0x41abf3=_0x41abf3-0x0;let _0x4457dc=a0_0x2a54[_0x41abf3];return _0x4457dc;};let c=a0_0x4457('0x2');let i=0x0;let n1=parseInt('0x'+c[0x0]);while(!![]){let s=s1[a0_0x4457('0x1')](c+i);if(s[n1]===0xb0&&s[n1+0x1]===0xb){document['cookie']=a0_0x4457('0x0')+c+i+'; path=/';break;}i++;};" \ - "window.setTimeout(function(){window.location.reload()}, 3000);" \ - "}" \ - "}" \ + ";window.setTimeout(function(){window.location.reload()}, 3000)}}" \ "</script>" \ "%s" \ "</body>" \ From 3a089d5c94eed33e56e089666dfc07f8127b537c Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Fri, 12 Apr 2024 20:52:49 +0200 Subject: [PATCH 08/15] Change to titles and verification messages --- ngx_http_js_challenge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ngx_http_js_challenge.c b/ngx_http_js_challenge.c index d7c6716..8e47f66 100644 --- a/ngx_http_js_challenge.c +++ b/ngx_http_js_challenge.c @@ -36,7 +36,7 @@ "</html>" -#define DEFAULT_TITLE "Verifying your browser..." +#define DEFAULT_TITLE "Browser Verification" static int is_private_ip(const char *ip) { struct in_addr addr; @@ -258,7 +258,7 @@ int serve_challenge(ngx_http_request_t *r, const char *challenge, const char *ht static const ngx_str_t content_type = ngx_string("text/html;charset=utf-8"); if (html == NULL) { - html = "<h2>Please wait...</h2>"; + html = "<h2>Your connection is being verified. Please wait...</h2>"; } size_t size = snprintf((char *) buf, sizeof(buf), JS_SOLVER_TEMPLATE, title_c_str, challenge_c_str, html); From fe542d30ba514ff9b14b9183980023e7d6e58b2a Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <5645408+ajarmoszuk@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:19:20 +0200 Subject: [PATCH 09/15] Create nginx-module-build.yml --- .github/workflows/nginx-module-build.yml | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/nginx-module-build.yml diff --git a/.github/workflows/nginx-module-build.yml b/.github/workflows/nginx-module-build.yml new file mode 100644 index 0000000..464f83a --- /dev/null +++ b/.github/workflows/nginx-module-build.yml @@ -0,0 +1,46 @@ +name: Build NGINX Module + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y nginx libnginx-mod-http-lua gcc make + + - name: Clone NGINX source code + run: | + git clone https://github.com/nginx/nginx.git ~/nginx-source + echo "NGINX_PATH=~/nginx-source" >> $GITHUB_ENV + + - name: Run build script + run: | + chmod +x build.sh + ./build.sh + + - name: Package module + run: | + tar -czvf ngx_module.tar.gz -C ${NGINX_PATH}/objs *.so + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: ngx-module + path: ngx_module.tar.gz + + - name: Check module output + run: | + ls ${NGINX_PATH}/objs/*.so From dfb7bdf695e1c403f8b00258566a25ec42475265 Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <5645408+ajarmoszuk@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:20:37 +0200 Subject: [PATCH 10/15] Update nginx-module-build.yml --- .github/workflows/nginx-module-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nginx-module-build.yml b/.github/workflows/nginx-module-build.yml index 464f83a..65d4859 100644 --- a/.github/workflows/nginx-module-build.yml +++ b/.github/workflows/nginx-module-build.yml @@ -19,7 +19,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y nginx libnginx-mod-http-lua gcc make + sudo apt-get install -y nginx gcc make - name: Clone NGINX source code run: | From 4eb32c8af8712f6d851290d61e843c650923d86b Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <5645408+ajarmoszuk@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:23:32 +0200 Subject: [PATCH 11/15] Update nginx-module-build.yml --- .github/workflows/nginx-module-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nginx-module-build.yml b/.github/workflows/nginx-module-build.yml index 65d4859..1fda06b 100644 --- a/.github/workflows/nginx-module-build.yml +++ b/.github/workflows/nginx-module-build.yml @@ -23,7 +23,9 @@ jobs: - name: Clone NGINX source code run: | - git clone https://github.com/nginx/nginx.git ~/nginx-source + wget https://nginx.org/download/nginx-${VERSION}.tar.gz + tar -xvzf nginx-${VERSION}.tar.gz + mv nginx-${VERSION} nginx-source echo "NGINX_PATH=~/nginx-source" >> $GITHUB_ENV - name: Run build script From 6603028541322869da2b027822541a7064fd3dd8 Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <5645408+ajarmoszuk@users.noreply.github.com> Date: Sat, 13 Apr 2024 17:24:11 +0200 Subject: [PATCH 12/15] Update nginx-module-build.yml --- .github/workflows/nginx-module-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nginx-module-build.yml b/.github/workflows/nginx-module-build.yml index 1fda06b..e12e4b8 100644 --- a/.github/workflows/nginx-module-build.yml +++ b/.github/workflows/nginx-module-build.yml @@ -23,9 +23,9 @@ jobs: - name: Clone NGINX source code run: | - wget https://nginx.org/download/nginx-${VERSION}.tar.gz - tar -xvzf nginx-${VERSION}.tar.gz - mv nginx-${VERSION} nginx-source + wget https://nginx.org/download/nginx-1.25.4.tar.gz + tar -xvzf nginx-1.25.4.tar.gz + mv nginx-1.25.4 nginx-source echo "NGINX_PATH=~/nginx-source" >> $GITHUB_ENV - name: Run build script From 471bda71c97d86045589e94786bb80564f6f36b8 Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Sat, 13 Apr 2024 17:31:00 +0200 Subject: [PATCH 13/15] Build process updated --- .github/workflows/nginx-module-build.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/nginx-module-build.yml b/.github/workflows/nginx-module-build.yml index e12e4b8..0f03ba4 100644 --- a/.github/workflows/nginx-module-build.yml +++ b/.github/workflows/nginx-module-build.yml @@ -21,15 +21,11 @@ jobs: sudo apt-get update sudo apt-get install -y nginx gcc make - - name: Clone NGINX source code + - name: Run build script run: | wget https://nginx.org/download/nginx-1.25.4.tar.gz tar -xvzf nginx-1.25.4.tar.gz - mv nginx-1.25.4 nginx-source - echo "NGINX_PATH=~/nginx-source" >> $GITHUB_ENV - - - name: Run build script - run: | + echo "NGINX_PATH=~/nginx-1.25.4" >> $GITHUB_ENV chmod +x build.sh ./build.sh From 89f1faf86d160f1ceb1b6ea53356d23e16ac67cd Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Sat, 13 Apr 2024 17:33:42 +0200 Subject: [PATCH 14/15] Trying again to fix the workflow --- .github/workflows/nginx-module-build.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nginx-module-build.yml b/.github/workflows/nginx-module-build.yml index 0f03ba4..6fb23c4 100644 --- a/.github/workflows/nginx-module-build.yml +++ b/.github/workflows/nginx-module-build.yml @@ -21,11 +21,14 @@ jobs: sudo apt-get update sudo apt-get install -y nginx gcc make - - name: Run build script + - name: Download and extract NGINX source run: | wget https://nginx.org/download/nginx-1.25.4.tar.gz tar -xvzf nginx-1.25.4.tar.gz - echo "NGINX_PATH=~/nginx-1.25.4" >> $GITHUB_ENV + echo "NGINX_PATH=$(pwd)/nginx-1.25.4" >> $GITHUB_ENV + + - name: Run build script + run: | chmod +x build.sh ./build.sh From a0ee87984df18b1c59d4114df0246339f5af8187 Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk <alex@jarmosz.uk> Date: Sat, 13 Apr 2024 17:36:33 +0200 Subject: [PATCH 15/15] Fixing the module finding --- .github/workflows/nginx-module-build.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nginx-module-build.yml b/.github/workflows/nginx-module-build.yml index 6fb23c4..e7e0aff 100644 --- a/.github/workflows/nginx-module-build.yml +++ b/.github/workflows/nginx-module-build.yml @@ -3,7 +3,7 @@ name: Build NGINX Module on: push: branches: - - master + - master # Ensure this matches your main branch name pull_request: branches: - master @@ -32,16 +32,21 @@ jobs: chmod +x build.sh ./build.sh + - name: List modules + run: | + ls ${NGINX_PATH}/objs + - name: Package module run: | - tar -czvf ngx_module.tar.gz -C ${NGINX_PATH}/objs *.so + find ${NGINX_PATH}/objs -name "*.so" -exec tar -czvf ngx_http_js_challenge_module.tar.gz -C ${NGINX_PATH}/objs {} + - name: Upload Artifact uses: actions/upload-artifact@v3 with: - name: ngx-module - path: ngx_module.tar.gz + name: ngx_http_js_challenge_module + path: ngx_http_js_challenge_module.tar.gz - name: Check module output run: | ls ${NGINX_PATH}/objs/*.so +