🤩 Get 50% off a full stack of courses and books

Bulletproof Your Drupal Website

Parvesh Aggarwal

“When you steal money or goods, somebody will notice it’s gone. When you steal information, most of the time no one will notice because the information is still in their possession.” – Kevin Mitnick, The Art of Deception, 2003.

It’s a worst-case scenario: one fine morning you notice your site has been or is being attacked. Broken security, of course, and usernames, passwords, emails (all three at times) … in short, sensitive information gets revealed to allow an outsider log in to your bank accounts and do all sort of crummy things like making your site relay spam or turning it into a malware den that infects thousand other visiting computers.

It is a fact that large numbers of websites lack secure configuration. For a site built with Drupal, logical/programmatic vulnerabilities in contributed (or, customized) modules can make it worse. The problem is that it is a very big task tracking down all the vulnerabilities attackers might use. The list is enormous … and growing.

I’m going to help you cover the most essential, the most common and the most important vulnerabilities. These steps will help you protect your Drupal sites by reviewing and finding the weaknesses and addressing them. Later, we’ll also look at a few steps to put up some extra protection.

I should point out that I am assuming familiarity with Drupal basics. Explanations of terms used can be found in the Drupal documentation. This will allow me to focus on explaining the concepts involved.

Drupal vulnerabilities

Insecure configurations

Drupal core is very secure by default, but vulnerabilities can open unknowingly if the configuration is wrong. There are possibly as many best practices as there are users, but certain settings are common in a secured Drupal environment.

Start by limiting the access and rights of anonymous users, including account creation. This will stop spammers and Google link-jackers. Some spammers target creating accounts, while the others posts links to their own sites from yours for a better search engine ranking.

Anonymous users create bogus accounts with automated programs (bots) and in a Drupal setting attackers can create accounts freely, since Drupal discriminates only between authenticated and unauthenticated users. An unauthenticated (or, anonymous) user creating an account effectively obtains privileges.

Next, disallow any content from being posted anonymously. Stopgaps will keep bots from finding your site, which means no flooding with spam content. Creating content or uploading thus must get some check or verification.

Put a limit to uploads, and also on the file types. It is possible with Drupal to set file types to be allowed, so no uploading PHP code. They might execute on the server or could be executable malware, harmfully dynamic content etc. Disallowing the upload of scripts and executables secures a site greatly.

Error reporting must be curbed. Drupal code results in errors sometimes and, by default, Drupal logs it in its internal error log. The same also gets displayed on the screen, but this is required only in a development environment. What’s of use to programmers is completely useless to end users. Moreover, the debugging messages contain critical configuration info that attackers may make good use of. This is information disclosure vulnerability.

Cross Site Scripting

XSS codes execute inside the browser. The code can be JavaScript, Flash or something similar. XSS uses your session cookies to gain access to everything you have. Visiting a page with a malicious XSS code running may result in new content posting, unknowingly befriending other sites’ users, casting votes and – most annoyingly – changing your own administrative rights!

Identifying XSS mostly involves identifying JavaScript vulnerabilities. However, others are not to be ignored. The trick that does it is using a JavaScript alert box. It’s hard to miss when it pops up, especially when the alarm is particular and specific to the place where code is injected. This is helpful for trace-backs.

You can do it with these two strings, posted into information and then browsing around your site. The "> helps finding if the code has an HTML attribute. This will pop-up a JavaScript box with a message coming from the title field of a blog node. Go to the page’s HTML, find where the JavaScript leaked through and back trace to the code to add a suitable filter function.


"><img src="u.png" onerror="alert('blog-node-title');"</script>

Drupal also allows configuration changes to fight XSS. To do that, you must not use the PHP input type. Drupal has three default formats (full HTML, filtered HTML and PHP) that users utilize when filling up web forms. Filtered HTML is by far the safest and should be used whenever possible to strip out malicious HTML codes (for example, JavaScript).

This will ensure safety against XSS (cross-site scripting) and also against CSRF (cross-site request forgery) attacks. While PHP input-type allows users write PHP directly into the existing content and allow the server to evaluate, it also lures attackers. In case there’s a need, enable permissions till it’s crafted. In short, it must stay disabled when not in use.

Cross Site Request Forgeries


The Drupal Form API puts up protection against CSRF. It uses special tokens (in the forms) that are updated constantly. With a module using the Form API for all data-modifying requests and the Form API documentation properly followed, there’s little chance for CSRF to create trouble. For good code, see the Forms API documentation. Menu callbacks, vulnerable to CSRF, are easily dealt with by adding confirm_form() (a confirmation form) to each. Drupal does the rest.

Apart from disabling PHP input-type, you may use this trick and directly leverage token generation in Drupal. It involves adding a token; the added token will be returned with a request for the following action and validated before the action is taken. The token is added to the security_review_reviewed function links and then checked in security_review_toggle_check .

A token is a special parameter added to a request. It tells Drupal that it generated a link for a specific user viewing the site. Tokens are added by Drupal to every form it generates and offers invisible protection to the developers.

$token = drupal_get_token($check['reviewcheck']);
$link_options = array(
'query' => array('token' => $token),
'attributes' => array('class' => 'sec-rev-dyn'),
if (!drupal_valid_token($_GET['token'], $check_name)) {
return drupal_access_denied();

However, to deal with CSRF in Drupal contributed modules (for example, a link like <img src="http:// website-name/yourmodule/shoes/123/delete" alt="naked girls pic" />), form confirmations along with form tokens are of paramount importance. These ask Are you sure you want to delete this item? for Drupal core and modules.

So, the bottom line is it is smart to use token protection when generating links and forms manually.

SQL Injection and the database API

Less trustworthy data in a database query (for example, feeds, user inputs, some other database etc.) must not be used directly. You stand a chance of being baked if you use something like this:

index.php?id=12mysql_query("UPDATE mytable SET value = '". $value ."' WHERE id = ".  $_GET['id']);

Or, say, when you combine two strings of data to form a single one (concatenating) that goes in directly into the SQL queries. For example,

db_query('SELECT FROM {table} t WHERE t.name = '. $_GET['user']);

To counter SQL injection, use Drupal functions and pass the user input treating them as parameters. It goes like this:

db_query("UPDATE {mytable} SET value = :valueWHERE id = :id", array(  ':value' => $value,  ':id' => $id);

Also, using the database abstraction layer helps to ward-off the attacks; besides, with db_query it uses proper argument substitution.

db_query("SELECT foo FROM {table} t WHERE t.name = '%s' ", $_GET['user']);

To accommodate arguments in variable numbers in SQL, creating a placeholders array helps. So, instead of:

db_query("SELECT t.s FROM {table} t WHERE t.field IN (%s)", $from_user);

It’s better to put:

$placeholders = implode(',', array_fill(0, count($from_user), "%d"));
db_query("SELECT t.s FROM {table} t WHERE t.field IN ($placeholders)", $from_user);

Using db_rewrite_sql() function calls with SQL statements referring to nodes (or {node}table) will stop violation of node access restrictions. It is an absolute requirement for the mechanism that makes Drupal accesses its nodes; any violation and it is outsiders gaining access to forbidden nodes. It’s done this way:

$result = db_query(db_rewrite_sql("SELECT n.nid, n.title FROM {node} n"));


This was sort of a crash-course on the many kinds of vulnerabilities that drive Drupal users mad, but anyone who has been into these rough waters will know that we have covered the three most important issues, namely – XSS, CSRF and SQL injection, out of which the first two can be easily dealt with if the recommendations on Drupal configuration are properly followed.

Let me know in the comments about your experiences with Drupal security, and whether you’d like to see more on the topic from me.