Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Detecting Homepage Defacement With Active Health Checks

DZone's Guide to

Detecting Homepage Defacement With Active Health Checks

Don't let hackers graffiti your site! Learn how to use NGINX as a means of keeping your site clear of homepage defacement attacks.

· Security Zone
Free Resource

Address your unique security needs at every stage of the software development life cycle. Brought to you in partnership with Synopsys.

Running the public‑facing website for any organization is no easy task. Considered by the business to be mission critical, a website requires 100% uptime and flawless stability. And yet, the same website is also expected to be updated frequently to meet the needs of the organization. This dichotomy of requirements between stability and constant change is one of the key challenges facing operations teams. But perhaps a bigger challenge is defending against bad actors, hackers, and "script kiddies."

Homepage defacement is the digital equivalent of graffiti across the front of your storefront or headquarters. Homepage defacement is a constant threat, and when it happens, it can make for unwanted attention and much embarrassment. Those responsible for running the website may also find that homepage defacement can result in a difficult discussion with the CEO, or even become a career‑limiting event.

Security requires a multi‑layered approach. In this blog post, we'll discuss how NGINX Plus can be used as part of a deep security stack that might also include firewalls, intrusion detection systems, and robust account management. We use the active health check capabilities of NGINX Plus to proactively monitor the website homepage for unexpected changes and to automatically serve the previous version if any such changes are detected.

Note: Active health checks with NGINX Plus are typically used for testing the general availability and end‑to‑end application health of backend systems. In this blog post, we'll look at a security use case which can be used to supplement existing health checks. For more information on configuring health checks, see the Admin Guide.

Detecting Changes With ETag

To explore how NGINX Plus can be used to detect homepage defacement, we'll use a simple test environment that represents the key components of a public‑facing website.

This simple solution uses the HTTP ETag of the homepage resource on the backend web server. Typically used for detecting changes to cached resources, by definition, the ETag value will change whenever the resource changes. We can obtain the ETag value by examining the headers for the home page.

$ curl -I http://10.0.0.1/
HTTP/1.1 200 OK
Date: Fri, 21 Jul 2017 15:05:25 GMT
Content-Type: text/html
Content-Length: 612
ETag: "58ad6e69-264"

We then define a health check that passes, provided that the same ETag is returned by the backend web server.

match homepage_etag {
    header ETag = '"58ad6e69-264"';
}

upstream my_website {
    server 10.0.0.1:80;
    zone   health 64k; # Allow workers to share health info
}

server {
    listen 80;
    location / {
        proxy_pass   http://my_website;
        health_check match=homepage_etag;
    }
}

The block defines the conditions that must be met by the >health_check directive on line 14, in order to pass. Here we require that the HTTP header ETag exactly matches the value obtained from the backend web server. By default, the health_check directive tests the root URI (/) so we need no further configuration to check the homepage.

This configuration effectively prevents any unauthorized changes to the homepage from being served. If the ETag changes, the health check will fail, and the backend web server will be considered unavailable. Clearly, this level of protection can have undesirable consequences. A site that is offline is arguably just as bad as one that has been defaced. Furthermore, routine and planned changes will cause an outage until the ETag value is updated in the NGINX configuration.

To address these issues, we enable content caching within NGINX and configure it so that when the health check fails, we continue to serve the last known good version. This configuration has the additional benefit of improving overall website performance by having NGINX serve cached content directly, instead of continually fetching it from the backend web server.

proxy_cache_path /var/cache/nginx_hc levels=1:2 keys_zone=hc_cache:1m max_size=2g inactive=48h;

server {
    listen 80;
    location / {
        proxy_pass   http://my_website;
        health_check match=homepage_etag;

        proxy_cache           hc_cache;
        proxy_cache_valid     60m;
        proxy_cache_use_stale error;
    }
}

The proxy_cache_path directive on line 1 defines the location for the cached content. The keys_zone parameter defines a shared memory zone for the cache index (called hc_cache) and allocates 1 MB. The max_size parameter defines the point at which NGINX Plus starts to remove the least recently requested resources from the cache to make room for new cached items. The inactive parameter defines how long NGINX will keep the cached content on disk since it was last requested, regardless of Cache-Control attributes received from the backend web server.

Within the location block, the proxy_cache directive enables caching for this location by specifying the name of the shared memory zone used for storing the cache keys ( hc_cache). The proxy_cache_valid directive tells NGINX how long it can serve a resource from cache before it's considered to have expired. This value is overridden by any Cache-Control attributes received from the backend web server.

To meet the requirement of serving the last known good version of the homepage while the backend web server is considered unhealthy, we use the proxy_cache_use_stale directive to allow expired content to be served even when the backend is unavailable.

Scaling Out Change Detection With nginScript Hashing

Detecting changes to the ETag is a simple and effective solution, but it doesn't scale from reverse proxying to load balancing. In a load balancing environment, a single instance of NGINX Plus might receive different ETag values from each of the backend web servers even though the content is the same.

Instead of relying on the ETag, a more robust way of detecting unexpected changes is by using a cryptographic hash of the actual content served from each backend web server. To do this, we provide an adjacent web service to each backend web server that provides the SHA‑256 hash value for any given URL. This adjacent web service, our hash service, will be used only by the NGINX Plus health check to obtain a hash value for the current homepage content on each backend web server.

The backend web server doesn't need to run NGINX or NGINX Plus in order to provide the hash service. NGINX can be installed alongside the existing web server software for this purpose and requires very little configuration.

stream {
    js_include conf.d/hash_service.js;

    server {
        listen 4256;
        js_filter hashResponseBody;
        proxy_pass 127.0.0.1:80;
    }
}

The hash service is implemented as a simple TCP proxy within the Stream context and uses nginScript to perform the hash operation on the HTTP response received from the local web server on port 80. For more information about nginScript, see Introduction to nginScript. The code for the hash service appears below.

/*
 * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined
 * in FIPS 180-2
 * Version 2.2 Copyright Angel Marin, Paul Johnston 2000 - 2009.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for details.
 * Also http://anmar.eu.org/projects/jssha2/
 */
var hexcase=0;function hex_sha256(a){return rstr2hex(rstr_sha256(str2rstr_utf8(a)))}function hex_hmac_sha256(a,b){return rstr2hex(rstr_hmac_sha256(str2rstr_utf8(a),str2rstr_utf8(b)))}function sha256_vm_test(){return hex_sha256("abc").toLowerCase()=="ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}function rstr_sha256(a){return binb2rstr(binb_sha256(rstr2binb(a),a.length*8))}function rstr_hmac_sha256(c,f){var e=rstr2binb(c);if(e.length>16){e=binb_sha256(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binb_sha256(a.concat(rstr2binb(f)),512+f.length*8);return binb2rstr(binb_sha256(d.concat(g),512+256))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d<c.length;d++){a=c.charCodeAt(d);b+=f.charAt((a>>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d<c.length){a=c.charCodeAt(d);e=d+1<c.length?c.charCodeAt(d+1):0;if(55296<=a&&a<=56319&&56320<=e&&e<=57343){a=65536+((a&1023)<<10)+(e&1023);d++}if(a<=127){b+=String.fromCharCode(a)}else{if(a<=2047){b+=String.fromCharCode(192|((a>>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binb(b){var a=Array(b.length>>2);for(var c=0;c<a.length;c++){a[c]=0}for(c=0;c<b.length*8;c+=8){a[c>>5]|=(b.charCodeAt(c/8)&255)<<(24-c%32)}return a}function binb2rstr(b){var a="";for(var c=0;c<b.length*32;c+=8){a+=String.fromCharCode((b[c>>5]>>>(24-c%32))&255)}return a}function sha256_S(b,a){return(b>>>a)|(b<<(32-a))}function sha256_R(b,a){return(b>>>a)}function sha256_Ch(a,c,b){return((a&c)^((~a)&b))}function sha256_Maj(a,c,b){return((a&c)^(a&b)^(c&b))}function sha256_Sigma0256(a){return(sha256_S(a,2)^sha256_S(a,13)^sha256_S(a,22))}function sha256_Sigma1256(a){return(sha256_S(a,6)^sha256_S(a,11)^sha256_S(a,25))}function sha256_Gamma0256(a){return(sha256_S(a,7)^sha256_S(a,18)^sha256_R(a,3))}function sha256_Gamma1256(a){return(sha256_S(a,17)^sha256_S(a,19)^sha256_R(a,10))}function sha256_Sigma0512(a){return(sha256_S(a,28)^sha256_S(a,34)^sha256_S(a,39))}function sha256_Sigma1512(a){return(sha256_S(a,14)^sha256_S(a,18)^sha256_S(a,41))}function sha256_Gamma0512(a){return(sha256_S(a,1)^sha256_S(a,8)^sha256_R(a,7))}function sha256_Gamma1512(a){return(sha256_S(a,19)^sha256_S(a,61)^sha256_R(a,6))}var sha256_K=new Array(1116352408,1899447441,-1245643825,-373957723,961987163,1508970993,-1841331548,-1424204075,-670586216,310598401,607225278,1426881987,1925078388,-2132889090,-1680079193,-1046744716,-459576895,-272742522,264347078,604807628,770255983,1249150122,1555081692,1996064986,-1740746414,-1473132947,-1341970488,-1084653625,-958395405,-710438585,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,-2117940946,-1838011259,-1564481375,-1474664885,-1035236496,-949202525,-778901479,-694614492,-200395387,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,-2067236844,-1933114872,-1866530822,-1538233109,-1090935817,-965641998);function binb_sha256(n,o){var p=new Array(1779033703,-1150833019,1013904242,-1521486534,1359893119,-1694144372,528734635,1541459225);var k=new Array(64);var B,A,z,y,w,u,t,s;var r,q,x,v;n[o>>5]|=128<<(24-o%32);n[((o+64>>9)<<4)+15]=o;for(r=0;r<n.length;r+=16){B=p[0];A=p[1];z=p[2];y=p[3];w=p[4];u=p[5];t=p[6];s=p[7];for(q=0;q<64;q++){if(q<16){k[q]=n[q+r]}else{k[q]=safe_add(safe_add(safe_add(sha256_Gamma1256(k[q-2]),k[q-7]),sha256_Gamma0256(k[q-15])),k[q-16])}x=safe_add(safe_add(safe_add(safe_add(s,sha256_Sigma1256(w)),sha256_Ch(w,u,t)),sha256_K[q]),k[q]);v=safe_add(sha256_Sigma0256(B),sha256_Maj(B,A,z));s=t;t=u;u=w;w=safe_add(y,x);y=z;z=A;A=B;B=safe_add(x,v)}p[0]=safe_add(B,p[0]);p[1]=safe_add(A,p[1]);p[2]=safe_add(z,p[2]);p[3]=safe_add(y,p[3]);p[4]=safe_add(w,p[4]);p[5]=safe_add(u,p[5]);p[6]=safe_add(t,p[6]);p[7]=safe_add(s,p[7])}return p}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)};

/*
 * nginScript js_filter function
 * Returns the hashed value of the response body as a header with no body
 */
var res = '';
function hashResponseBody(s) {
    if (s.fromUpstream) {
        res = res + s.buffer;
        s.buffer = '';
        var body_pos = res.indexOf("\r\n\r\n"); // Find where the headers end

        if (body_pos) {
            var hash = hex_sha256(res.substr(body_pos + 4)); // Skip CRLFCRLF

            s.buffer  = "HTTP/1.1 204 No Content\n";
            s.buffer += "Hash: " + hash + "\n";
            s.buffer += "\r\n\r\n";
        }
    }
    return;
}

With this configuration in place, we obtain the SHA‑256 hash of the homepage by querying the hash service or by sending the homepage output to an external hashing library. Here we use OpenSSL to check if the results of the hash service are correct.

$ curl -I http://127.0.0.1:4256/
HTTP/1.1 204 No Content
Hash: 38ffd4972ae513a0c79a8be4573403edcd709f0f572105362b08ff50cf6de521

$ curl -s http://127.0.0.1/ | openssl dgst -sha256
(stdin)= 38ffd4972ae513a0c79a8be4573403edcd709f0f572105362b08ff50cf6de521

The hash service returns a minimal HTTP response, with only the hash value as a single HTTP header. The NGINX Plus configuration can now be modified to use the hash service for its health check.

atch homepage_hash {
    status 204;
    header hash = 38ffd4972ae513a0c79a8be4573403edcd709f0f572105362b08ff50cf6de521;
}

upstream my_website {
    server 10.0.0.1:80;
    server 10.0.0.2:80;
    server 10.0.0.3:80;
    zone   health 64k; # Allow workers to share health info
}

proxy_cache_path /var/cache/nginx_hc levels=1:2 keys_zone=hc_cache:1m max_size=2g inactive=48h;

server {
    listen 80;
    location / {
        proxy_pass   http://my_website;
        health_check match=homepage_hash port=4256;

        proxy_cache           hc_cache;
        proxy_cache_valid     60m;
        proxy_cache_use_stale error;
    }
}

The match block now defines a healthy response as one which includes a status code of 204 and a header of hash, with the value obtained from the hash service. In order for NGINX Plus to use the hash service on each backend web server, the health_check directive specifies the port number used by the hash service, which overrides the port specified in each server directive in the upstream group (lines 7-9).

We can expect the homepage content to be identical for each backend web server, so NGINX Plus removes a backend server from the upstream group if the homepage content changes. However, NGINX Plus continues to serve the last known good content from the cache so that users are unaffected. The NGINX Plus dashboard can then be used to highlight any backend servers that have failed health checks - another way in which NGINX Plus helps you deliver your site with performance, security, reliability, and scale.

Find out how Synopsys can help you build security and quality into your SDLC and supply chain. We offer application testing and remediation expertise, guidance for structuring a software security initiative, training, and professional services for a proactive approach to application security.

Topics:
security ,web application security ,homepage defacement ,cybersecurity ,cyberattack

Published at DZone with permission of Liam Crilly, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}