From 94458e13dc9b9c16310695dc31186b697a7a6e33 Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk Date: Fri, 12 Apr 2024 13:35:33 +0200 Subject: [PATCH 1/5] 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

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 #include +#include + #define DEFAULT_SECRET "changeme" #define SHA1_MD_LEN 20 #define SHA1_STR_LEN 40 @@ -17,16 +19,54 @@ "" \ "" \ "" \ "%s" \ "" \ "" + #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 *)"

Cookies Required

Please enable cookies in your browser to continue.

"; + 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 Date: Fri, 12 Apr 2024 17:31:05 +0200 Subject: [PATCH 2/5] 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 Date: Fri, 12 Apr 2024 20:15:04 +0200 Subject: [PATCH 3/5] 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 Date: Fri, 12 Apr 2024 20:34:26 +0200 Subject: [PATCH 4/5] 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 = '

Cookies are required to access this content.

Please enable cookies in your browser settings and try again.

';" \ " }" \ " } 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>2]|=t[r]<>2]|=i<>2]|=(192|i>>6)<>2]|=(128|63&i)<=57344?(a[e>>2]|=(224|i>>12)<>2]|=(128|i>>6&63)<>2]|=(128|63&i)<>2]|=(240|i>>18)<>2]|=(128|i>>12&63)<>2]|=(128|i>>6&63)<>2]|=(128|63&i)<=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>2]|=t[r]<>2]|=i<>2]|=(192|i>>6)<>2]|=(128|63&i)<=57344?(a[e>>2]|=(224|i>>12)<>2]|=(128|i>>6&63)<>2]|=(128|63&i)<>2]|=(240|i>>18)<>2]|=(128|i>>12&63)<>2]|=(128|i>>6&63)<>2]|=(128|63&i)<=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)}}" \ "" \ "%s" \ "" \ From 3a089d5c94eed33e56e089666dfc07f8127b537c Mon Sep 17 00:00:00 2001 From: Alex Jarmoszuk Date: Fri, 12 Apr 2024 20:52:49 +0200 Subject: [PATCH 5/5] 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 @@ "" -#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 = "

Please wait...

"; + html = "

Your connection is being verified. Please wait...

"; } size_t size = snprintf((char *) buf, sizeof(buf), JS_SOLVER_TEMPLATE, title_c_str, challenge_c_str, html);