Night Hour

Reading under a cool night sky ... 宁静沉思的夜晚 ...

Web Application Security Headers

Gecko lizard image

Absence of evidence is not evidence of absence. , Carl Sagan


5 March 2017


Introduction

Web application security requires much efforts. It involves the entire application life cycle. From requirements, design and development, testing and deployment, and the final decommissioning. One common way to improve web application security is through the use of web security headers. If you are running a web server like Nginx, Apache httpd etc..., setting up proper HTTP security headers is not difficult and can help to improve security for users who are using modern browsers that support such headers.

For example, setting the X-Frame-Options header can prevent clickjacking. Setting HTTP Strict Transport Security (HSTS) can help ensure encrypted connection, and Content Security Policy (CSP) header can prevent Cross-Site Scripting (XSS) etc... Many leading websites have already implemented these headers and others are now starting to follow. This article shows how some of these headers are configured in the Apache httpd web server that this website is running on. Hopefully this can provide some examples and encourage others to enhance their web security.

Cache Control Header

This header controls whether responses from the web server are stored in intermediate and local caches and is usually meant to improve performance and response time. However, there can be security implications in storing information in caches particularly if it involves sensitive data. For unencrypted web traffic that does not contain sensitive data, Cache Control is usually set to public.

In recent times, to protect user privacy and communication, a lot of websites are switching to HTTPS even when serving regular content. Nighthour.sg is no exception. Enabling TLS also prepares for the HTTP 2 protocol, the new web transfer standard. While the HTTP 2 standard doesn't mandate TLS (Transport Layer Security i.e. HTTPS), some of the major browser vendors intend to only support HTTP 2 over TLS.

The general setting for a HTTPS website is to set Cache Control as private for non sensitive data. This allows the browser to keep a local copy of the webserver response, hence improving performance. Intermediate shared caches or proxies that can inspect HTTPS traffic should not store the private content. The following Apache mod header directives set the Cache-Control header to be private with a max-age of 15 minutes (900 seconds) for html and text files. Images and resources such as stylesheet and javascript files are set with a cache expiry of up to 90 days.

Header setifempty Cache-control "private,max-age=900" "expr=%{REQUEST_URI} =~ m#.(htm|html|txt|ico)$#i"

Header setifempty Cache-control private,max-age=7776000 "expr=%{REQUEST_URI} =~ m#.(gif|png|jpg|svg)$#i"

Header setifempty Cache-control private,max-age=7776000 "expr=%{REQUEST_URI} =~ m#.(css|js)$#i"

Here is the link to the apache mod headers documentation, which provides more information on the Apache httpd "Header" directive.

For dynamic content from scripts or sensitive data sent from the webserver, the Cache-Control header should be set to no-store. This indicates that the response should not be stored in non-volatile cache storage by the browser or intermediate caches. This header may have to be set individually by the respective script file.

To learn more about Cache Control, this Google developer article has a good explanation on the various Cache-Control settings.

X-Frame-Options

This is a common web security header that is often mentioned in security testing and assessment. It controls whether a website can be placed inside html frames. Since Nighthour.sg doesn't use any frames, this header is set to DENY. Doing so will prevent other external websites from embedding nighthour.sg in html frames, thus prevent clickjacking.

Header set X-Frame-Options: "DENY"

Clickjacking is a technique that attempts to hijack user keystrokes, mouse movements etc...It aims to capture sensitive information or take over a user session. For websites that require frames, settings such as SAMEORIGIN or a list of allowable websites can be configured. More info on X-Frame-Options can be found at this Mozilla developer article.

Content Security Policy

Content Security Policy (CSP) is a relatively new security header but some leading websites are already implementing this. If you run a technology and security focused site, you should probably be configuring this and set an example for others. Security professionals have to eat their own dog food.

Content Security Policy is a very useful header that can help prevent Cross-Site Scripting (XSS) and many other attacks. It is important to note that the user browser will have to support this header in order to offer protection. In fact, this is required for all the web security headers described here. Modern up to date browsers should support most of the headers described here. The Can I use website offers useful tables on browser support for various headers and web technologies.

IE 11 doesn't support the standard CSP header, rather it offers limited support using "X-Content-Security-Policy" header. If you are using IE11 consider switching to a more modern and standard compliant browser.

The following are the CSP directives configured for this site.

Header always set Content-Security-Policy "default-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; report-uri /csprp/;"

Header always edit Content-Security-Policy "default-src 'self' ;style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; report-uri /csprp/;" "expr=%{REQUEST_URI} =~ m#.(svg)$#i"

The "default-src 'self'" line tells the browser that javascript, css stylesheets should only originate from this site itself, no external references to third party scripts or css stylesheets are allowed. Furthermore inline scripts and stylesheets are disallowed. Inline means the script or css are on the html page itself rather than included from a separate file.

Notice the second edit entry ? This entry wlll edit the original CSP entry if the file requested is a SVG image. For SVG image, inline style is required otherwise, some SVG images that rely on inline stylesheets will not display properly.

There is a "report-uri /csprp/" in the Content Security Policy header shown above. This is for reporting policy violations and can alert a security team or a website administrator to potential security issues on the web application. When a browser encounters a resource on the website that violates the configured security policy, it will send a violation report to the report-uri. This also provides a useful way to troubleshoot issues when creating CSP policies.

Here is a screenshot of firefox browser blocking/stopping a potential Cross-Site Scripting (XSS) from a php script that doesn't verify input or sanitize output properly.

CSP violation in browser console
Fig 1. Firefox showing a CSP violation error

Firefox will post a report to the report-uri specified. The following screenshot shows firefox posting the Json based violation report to https://www.nighthour.sg/csprp/. At this report-uri, /csprp/ , a simple php script is set up to receive the report.

CSP violation in browser console
Fig 2. Firefox posting a CSP violation to report-uri /csprp/

The following list the php script for receiving the CSP violation reports. It uses phpmailer to send a violation alert to the site administrator. There is a throttling mechanism to prevent excessive submissions and spam reports. Refer to this earlier article Creating a Finite State Php Rate Limiter for more information on the rate limiting mechanism and how to actually create a simple php rate limiter. The github links at the bottom of the article provides the source code for this CSP script as well as the throttle script.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<?php

/*
* MIT License
*
* Copyright (c) 2017 Ng Chiang Lin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/


/*

Content Security report end point
to receive reports on CSP violations

Uses the phpmailer to send email notifications on CSP violations. 
Refer to https://github.com/PHPMailer/PHPMailer for more detailed information on 
how to use phpmailer

There is a throttle mechanism in this script to control the rate of reports.
This is to prevent spamming. 
Refer to https://www.nighthour.sg/articles/2017/php-rate-limiter-finite-state.html
for the throttling implementation

Ng Chiang Lin
March 2017

*/

require_once '<php scripts library location>/throttle.php';
require_once '<php scripts library location>/class.phpmailer.php';
require_once '<php scripts library location>/class.smtp.php';


/* Send email notification of the violation */
function sendEmail($from, $to, $subject, $message)
{
    $mail = new PHPMailer;
    $mail->isSMTP();                                     
    $mail->Host = 'localhost'; 
    $mail->Port = 25;
    $mail->SMTPAuth = false;
    $mail->SMTPOptions = array(
        'ssl' => array(
              'verify_peer' => false,
              'verify_peer_name' => false,
              'allow_self_signed' => true
          )
     );
    $mail->CharSet = 'UTF-8'; // Set charset to utf-8
    $mail->setFrom($from, 'CSP Violation Mailer');
    $mail->addAddress($to, 'CSP Violation Mailer');
    $mail->isHTML(false); //To prevent any potential malicious content, set it to text email. 
    $mail->Subject = $subject;
    $mail->Body   =  $message;
      
    if(!$mail->send()) 
    {          
        error_log("Error sending CSP email !\n" . "Mailer Error: " . $mail->ErrorInfo  . "\n", 0);
        return false;        
    } 
    return true;
}

define("MAXSIZE", 250000);


function allow($ip, $result)
{
    $raw = file_get_contents("php://input");
    $content = null;
    $content = json_decode($raw, true, 5);

    $message ="";
    $formatted="";
    
    if(!is_null($content) && strlen($raw) < MAXSIZE )
    { //Valid Json 
        $report=key($content);
        if( ! is_null($report)  and  strcasecmp("csp-report", $report) ===0 )
        {//Another check to make sure the first json name is "csp-report"
     
     
            $formatted = json_encode($content, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
            $message = $_SERVER['REMOTE_ADDR'] . "\n" . $_SERVER['HTTP_USER_AGENT'] . "\n\n" 
                        . $formatted . "\n";              
             
            sendEmail('no-reply@nighthour.sg','myreceivingemailaddress@nighthour.sg','CSP Violation Report', $message);
            http_response_code(202); //Send HTTP 202 accepted back
     
        }
        else
        {
            error_log("Invalid CSP Json format: " . $_SERVER['REMOTE_ADDR'] . " : " . $_SERVER['HTTP_USER_AGENT'] . "\n" , 0);
            exit(1); 
        }

    }
    else
    {
        error_log("Invalid CSP Json format: " . $_SERVER['REMOTE_ADDR'] . " : " . $_SERVER['HTTP_USER_AGENT'] . "\n" , 0);
        exit(1); 
    }
    
}  


function disallow($ip, $result)
{
    error_log("CSP report violation exceeded throttle rate ! : " . " count : " . $result['count']
    . " : " . $_SERVER['REMOTE_ADDR'] . " : " . $_SERVER['HTTP_USER_AGENT'] . "\n", 0); 
    http_response_code(202); //Send HTTP 202 accepted 
    exit(1);
}



if( isset($_SERVER['REQUEST_METHOD'])  &&  strcasecmp("post", $_SERVER['REQUEST_METHOD'] ) == 0   )
{//http post


     //Starts the finite state throttling, calls allow() if rate is within limit
    startThrottleStateMachine($_SERVER['REMOTE_ADDR']); 
    header('Cache-control: no-store');
    header('Content-Type: text/html; charset=UTF-8');

    
}
else
{
    //Any other methods directed to error page
    header("Location: https://www.nighthour.sg/error.html");
    exit(1);
}


?>

Here are some useful links that provides more information about Content Security Policy.
https://developers.google.com/web/fundamentals/security/csp/
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

HTTP Strict-Transport-Security (HSTS)

This security header is meant to help enforce encrypted website connections (TLS) and reduce the risk of network eavesdropping and man-in-the-middle attacks. HTTP Strict-Transport-Security (HSTS) also prevent a case where a user enter http://www.nighthour.sg in their browser location bar instead of https://www.nighthour.sg, or a search link containing the unecrypted http url. A man-in-the-middle attack that tries to strip away or forge a TLS connection of a website can go unnotice by a user. HSTS can help to reduce the chances of this succeeding.

The HSTS header tell a suppprting browser that a website supports TLS and the browser should always use TLS to connect to the website. If a user types http instead of https, the browser will replace it with https. The browser will also terminate insecure connection, such as a man-in-the-middle proxy that attempts to forge TLS certificate which is invalid. A weakness of HSTS is that the first time a user connects to website or when the HSTS header has expired, it may be via unencrypted HTTP, thus allowing a man-in-the-middle attack.

HSTS header has a max-age setting where it will be effective. After the max-age expires, if a user types http://, the browser will allow the plain unencrypted connection until it receives a new HSTS from the server. Each time a browser receives a HSTS header it will refresh the expiry and extends the validity of HSTS. Potentially this can prevent a HSTS from ever expiring if a user visits a site regularly.

This site sets the following HSTS header. The max age is 1 year and all subdomains are included (connections to all subdomains requires TLS as well). This setting should be configured for TLS enabled host in apache. The header should not be sent over HTTP, it has to be sent over a secure HTTPS connection.

Header always set Strict-Transport-Security "max-age=31536000;includeSubDomains"

To address the weakness of HSTS where the first connection attempt can be through HTTP, a preloaded list can be used. The Chrome project supports a preloaded list of websites that should always use TLS. The list is shipped with the browser. Website owners can submit their domain to Chrome to be included. More info about this can be found here. Nighthour.sg currently does not use preloading.

Another thing to note about HSTS header is that it may not persist through incognito or privacy mode of some browsers. If a user uses incognito or private browsing, then the HSTS may not be saved and the browser will fetch the first request via normal unencrypted HTTP.

Some intermediate proxies can inspect TLS traffic. So if you are running a site that contains really sensitive information, additional mitigations may be required. One possible technique is to have one more layer of client side encryption, using either asymmetric or symmetric cryptography or a combination of both. For example, smartcards can be used to encrypt sensitive data before transmission to the web application. The encrypted data is then sent over the TLS transport. Alternatively, a VPN (Virtual Private Network) can be used and the TLS web traffic only travels through this additional encrypted tunnel.

There is some debate over whether it is useful to have multiple layers of encryption. This author believes in defense in depth. Depending on how sensitive and critical the data is, additional layers of encryption won't hurt. (Eg. Using a smartcard to encrypt extremely sensitive data and transmitting it over TLS to a remote web application, through a VPN connection) Besides, many TLS vulnerabilities have been reported in recent years. For the most sensitive data, it will be prudent not to solely rely on a single encryption method.

This Mozilla article contains more details about HSTS header.

Content-Type Character Encoding

Content-Type header tells a browser what sort of MIME data it is receiving, whether it is text/html , application/javascript or image/jpeg etc... For textual content, such as text files, html files, javascript or css, it is a good idea to configure the default character set, so that the browser can interpret the content properly. Some attacks make use of unclear character encoding, to bypass filters and checks.

Many websites nowadays need to support multiple languages, it is a good idea to use a universal character encoding that can support the diverse languages on the internet. This site uses UTF-8 and set this as the default character set in Apache Http Server.

AddDefaultCharset UTF-8

X-XSS-Protection

This header tells a browser to stop loading a page if it detects a reflected Cross-Site Scripting (XSS). Reflected cross site scripting is script that is entered by a user, which is returned to the browser as part of the server response. With the newer Content Security Policy (CSP) header, this header may no longer be necessary. It is set to mainly support older browsers. Refer to this mozilla link for more information on this header.

The following is the setting configured on Apache Httpd for this website

Header set X-XSS-Protection "1; mode=block"

X-Content-Type-Options

This header tells a browser to accept the MIME type (eg. text/html) that is present in the Content-type header of a server response. It stops a browser from trying to examine the data and determine what type of content it is. This helps to prevent data mis-identification by a browser. Mis-identifying data can lead to attacks. This msdn article has a more detailed description of the issue. The following mozila article contains more information on the X-Content-Type-Options header.

The following is the setting configured in Apache Httpd for this website.

Header set X-Content-Type-Options: "nosniff"

Apache Server Signature

These headers tell the Apache Webserver whether to send server information such as version number in web responses. Version numbers can be used by malicious attackers to discover known vulnerabilities against the software. A common security practice is to send only minimal server information, if not none at all. While this may seems like security through obscurity, in reality it is actually prudent advice. There is no good reason to provide malicious hackers with information about your systems and servers. This site sets the following.

ServerTokens Prod
ServerSignature Off

The first line tells Apache HTTP Server to send only the string "Apache" without any OS, modules and version information. The second line, ServerSignature off, tells Apache Httpd not to append server and version information in the Apache generated pages, such as error pages.

Apache Etags

The Etag header is used to determine whether a cached resource is still valid. However, Etag can potentially leak inode information and may also interfere with caching for load balanced systems. The setting for this site is to disable Etag and rely on the Last-Modified header to provide information about cache validity.

FileETag None

HTTP Referrer-Policy

Whenever a web visitor clicks on a link to an external site from a webpage, the browser will send a referer field, telling the external site where the link originates from. This allows the external site to know which website the user has visited before arriving here. To better protect user privacy, it may be good idea to disable or restrict such referer information. This website sets the following

Header always set Referrer-Policy "same-origin"

This tells supporting browsers to send referrer header only to the same website, nighthour.sg. Referrer header will not be sent to external websites. HTTP referrer is used by some of the scripts on nighthour.sg to do some simple checks against spamming and to prevent image hotlinking. HTTP referrer is therefore not disabled using the "no-referrer" value.

At the moment IE and Safari on iOS don't support this header. This Mozilla developer link provides more information on the Referrer-Policy header.

Cookie Settings

Cookies are frequently used by web applications to store session ids or to track user preferences. Cookies that store session ids or any sensitive information should be set with HTTPOnly and Secure flag.

The HTTPOnly flag disallows client side javascript from accessing the cookie. This prevents attackers from using Cross-Site Scripting (XSS) vulnerabilities in the website to extract sensitive cookie information. The Secure flag ensures that cookies are sent only over encrypted TLS connection. This can protect sensitive data and session ids stored on cookies.

Cookies that store session ids or sensitive information should not be persistent and saved on the browser. Such cookies should expire and be purged immediately once the current browsing session ends (user closes the browser). Such non-persistent cookies do not set the expire flag.

There is a new cookie flag, Samesite, which is meant to prevent Cross-Origin Information leakage and Cross-Site Request Forgery (CSRF). Older browsers may not support this header. So get your users to upgrade to newer modern browsers. This is a good security flag to add when an application sets a sensitive cookie.

Nighthour.sg does not use any persistent cookies to track users. It may use session cookies to provide services to users and visitors. The session cookies are non persistent, valid only for the current browsing session and configured with Secure and HttpOnly flag. In additional to these 2 flags, the CMS login page sets cookie with Samesite=strict as well.

If you are using php, there are three settings in php.ini to enable HttpOnly flag, Secure flag, as well as the cookie validity period. The cookie lifetime is set to 0, which means that it will be purged once the browser is close.

session.cookie_httponly = True
session.cookie_secure = 1
session.cookie_lifetime = 0

In newer versions of Php such as Php 7.3 in my Debian 10 server, the Samesite cookie flag can be set using the following.

session.cookie_samesite = Strict

The value of "Strict" means that third party websites cannot send cookies that are set by an original domain when initializing requests to that domain. This can prevent Cross Site Request Forgery (CSRF) attacks.

For example, a user logs into a banking site, https://mybank.sg. Then the user visits a malicious website, http://malicioushacker.sg, and malicioushacker try to submit a fund transfer request to mybank.sg through some hidden scripts. A browser that supports Samesite cookie, will not send the session cookie from mybank.sg when the request is initialized by scripts at malicioushacker.sg. This prevents the malicious fund transfer request, stopping the CSRF attack.

If you are using an older version of php that doesn't support the Samesite flag, you can actually set this manually in your application. The following shows an example.

1
2
3
4
5
6
7
8
session_start(); 

if ( empty($_COOKIE['<Session Name>']) )
{//no session cookie set yet
   header_remove("Set-Cookie");
   $sessioncookie = "Set-Cookie: PSID=" . session_id() . "; path=/; domain=nighthour.sg; secure; HttpOnly; SameSite=Strict" ;
   header($sessioncookie);
}

Additional application code will also have to handle the cases of session expiry etc... Here is a Mozilla developer article that provides more information about cookies. For information about Samesite cookie refer to this link MDN SameSite Cookie.

Conclusion and Afterthought

Web security headers can be a useful and effective way to help enhance the security of websites and web applications. While it may not replace the need for Secure Software Design and proper validation/sanitization of untrusted input etc...; it does provide an additional layer of security and is relatively easy to implement. Modern web browsers support many of these headers and website owners should consider implementing these to provide better protection for their users.

Useful References

A copy of the php rate limiter is available at this git respository
https://github.com/ngchianglin/PhpRateLimiter
The csp reporting script is available at this github link here, csp-violation-report.php.

If you have any feedback, comments, corrections or suggestions to improve this article. You can reach me via the contact/feedback link at the bottom of the page.

The nocturnal house gecko scrambles quickly up the side of a wall. It pauses for a moment, glancing warily for signs of danger; flicks out its tongue briskly, tasting the night air. Having detected no signs of danger, the gecko scuttles away into the cool night ...

Article last updated on Oct 2020.