If you have a varying number of values in your SQL that cannot be determined until runtime, you should use the $query-> condition (field, array of values, ‘IN’) statement to restrict your query to a dynamic list of values as defined in the second
parameter. Anexample of using this technique is as follows:
// $node_types is an array containing one or more node type names
// such as article, page, blog, etc.
$node_types = array('article', 'page', 'blog');
// Prepare and execute the query using the list of node types
$query = db_select('node', 'n');
$query->fields('n', array('title'));
$query->condition("n.type", $node_types, 'IN');
$query->condition("n.status", 1);
$query->addTag('node_access');
$result = $query->execute();
Permissions and Page Callbacks
Another aspect to keep in mind when writing your own modules is the access arguments key of each menu item you define in the menu hook. In the earlier example demonstrating insecure code, we used the following access arguments:
/*
* Implements hook_menu().
*/
function insecure_menu() {
$items['insecure'] = array( 'title' => 'Insecure Module', 'description' => 'Example of how not to do things.',
'page callback' => 'insecure_code','access arguments' => array('access content'),);
return $items;
}
It’s important to question who is allowed to access this callback. The “access content” permission is a very general
permission. You probably want to define your own permissions, using hook_permission(), and use those to protect your
menu callbacks.Permissions are unique strings describing the permission being granted (see the section “Access Control” in
Chapter 4 for more details).
Because your implementation of the menu hook is the gatekeeper that allows or denies a user the ability to reach the code behind it (through the callback), it’s especially important to give some thought to the permissions you use here.
Cross-Site Request Forgeries (CSRF)
Suppose that you have logged into drupal.org and are browsing the forums there. Then you get off on a tangent and endup browsing at another web site. Someone evil at that web site has crafted an image tag like this:
When your web browser loads the image, it will request that path from drupal.org. Because you are currently logged in to drupal.org, your browser will send your cookie along with the request. Here’s a question to ponder: when drupal.org
receives therequest, will it consider you a logged-in user with all the access privileges you’ve been given? You bet it will!
The evil person’s image tag has essentially made your user click a link on drupal.org.
The first defense against this type of attack is to never use GET requests to actually change things on the server; that way, any requests gened this way will be heratarmless. The Drupal form API follows the HTTP/1.1 convention that the GET
method should not take any action other than data retrieval. Drupal uses POST exclusively for actions that make changes
Second, the form API uses tokens and unique IDs to make sure that submitted form values from POST requests are
coming from a form that Drupal sent out (for more on this, see Chapter 11). When you are writing modules, be sure to
use the formAPI for your forms and you will gain this protection automatically. Any action that your module takes as a
result of form input should happen in the submit function for the form. That way, you are assured that the form API has protected you. Finally, you can also protect GET requests if necessary by using a token (generated by drupal_get_token())
in the URL and verifying the token with drupal_valid_token().
File Security
The dangers faced by Drupal when handling files and file paths are the same as with other web applications.
File Permissions
File permissions should be set in such a way that the user cannot manipulate (add, rename, or delete) files. The web server should have read-only access to Drupal files and directories. The exception is the file system paths. Clearly, the web server must have
access to those directories so it can write uploaded files.
Protected Files
The .htaccess file that ships with Drupal has the following lines:
# Protect files and directories from prying eyes.
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|É.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$"> Order allow,deny</FilesMatch>
The Order directive is set to allow,deny, but no Allow or Deny directives are included. This means that the implicit behavior is to deny. In other words, reject all requests for the files shown in Table 21-2.
Table 21-2. Files
Rejected by the FilesMatch Directive’s Regular Expression in Drupal’s .htaccess
File
|
|
Files Matched
|
Description
|
Ends with .engine
|
Template engines
|
Ends with .inc
|
Library files
|
Ends with .info
|
Module and
theme .info files
|
Ends with .install
|
Module .install files
|
Ends with .module
|
Module files
|
Ends with .make
|
Make files
|
Ends with .profile
|
Installation
profiles
|
Ends with .po
|
Portable object files (translations)
|
Ends with .sh
|
Shell scripts
|
Ends with .*sql
|
SQL files
|
Ends with .test
|
Test scripts
|
Ends with .theme
|
PHP themes
|
Ends with .tpl.php
|
PHP Template template files
|
Ends with .tpl.php4
|
PHP Template template files
|
Ends with .tpl.php5
|
PHP Template template files
|
Ends with .xtmpl
|
XTemplate files
|
Begins with Entries
|
CVS file
|
Named Repository
|
CVS file
|
Named Root
|
CVS file
|
Named Tag
|
CVS file
|
Named Template
|
CVS file
|
File Uploads
If a module is enabled to allow file uploading, the files should be placed in a specific directory, and access should be enforced by the code. If file uploads are enabled and the private download directory is set at Configuration -> File system, the file system path on
that same screen must be set to no public access.
Filenames and Paths
No filename or file path information from the user can be trusted! When you are writing a module and your code expects to
receive somefile.txt, realize that it may get something else instead, like
../somefile.txt // File in a parent directory.
../settings.php // Targeted file.
somefile.txt; cp ../settings.php ../settings.txt // Trying to run a shell command.
The first two examples try to manipulate the file path by including the two dots that indicate a parent directory to the underlying
operating system. In the last example, the programmer attempts to execute a shell command and has included a semicolon sothat
after the shell command runs, an additional command will run that will make settings.php readable and thus reveal the database
ually has write access to directories other than the file system path.username and password. All of the preceding examples are
hoping that file permissions are set incorrectly, and that the webserver act
Whenever you are using file paths, a call to file_valid_uri() is in order, like this:
if (!file_valid_uri($uri) {
// Abort! File URI is not what was expected!
}
The file_valid_uri() function will find out whether the URI has a valid scheme for file operations. In general, you probably don’t want the Next Great File Management Module to be your first Drupal project. Instead, study existing file-related modules that
have been around for a while.
Encoding Mail Headers
When writing any code that takes user input and builds it into an e-mail message, consider the following two facts:
• E-mail headers are separated by line feeds (only line feeds that aren’t followed by a space or tab are treated as header
separators).
• Users can inject their own headers in the body of the e-mail if you don’t check that their input is free of line feeds.
For example, say you expect the user to enter a subject for his or her message, and the user enters a string interspersed by
escaped line feed (%0A) and space (%20) characters:
Have a nice day%0ABcc:spamtarget@example.com%0A%0AL0w%20c0st%20mortgage!
The result would be as follows:
Subject: Have a nice day Bcc: spamtarget@example.com
L0w c0st mortgage!
...
For that reason, Drupal’s built-in mail function drupal_mail() in includes/mail.inc runs all headers through mime_
header_encode() to sanitize headers. Any nonprintable characters will be encoded into ASCII printable characters according
to RFC 2047, and thus neutralized. This involves prefixing the character with =?UTF-8?B? and then printing the Base64-encoded character plus ?=. You’re encouraged to use drupal_mail(); if you choose not to, you’ll have to make the
mime_header_encode() calls yourself.
Files for Production Environments
Not all files included in the distribution of Drupal are necessary for production sites. For example, making the CHANGELOG.txt
file available on a production site means that anyone on the Web can see what version of Drupal you are running (of course, the
black hats have other ways of detecting that you are running Drupal; see www.lullabot.com/articles/is-site-running-drupal).
Table 21-3 lists the files and/or directories that are necessary for Drupal to function after it has been installed; the others can be
removed from a production site (keep a copy, though!). Alternatively, read access can be denied to the web server.
Table 21-3. Files
and Directories That
Are Necessary for Drupal to Function
|
|
File/Directory
|
Purpose
|
.htaccess
|
Security, clean URL, and
caching support on Apache
|
cron.php
|
Allows regularly scheduled tasks to run
|
includes/
|
Function libraries
|
index.php
|
Main entry point for Drupal requests
|
misc/
|
JavaScript and
graphics
|
modules/
|
Core modules
|
robots.txt
|
Prevents well-behaved robots from hammering your site
|
sites/
|
Site-specific modules, themes, and
files
|
themes/
|
Core themes
|
xmlrpc.php
|
XML-RPC endpoint; necessary only if your site will
receive incoming XML-RPC
requests
|
authorize.php
|
Administrative script for running authorized file operations
|
SSL Support
By default, Drupal handles user logins in plain text over HTTP. However, Drupal will happily run over HTTPS if your web server supports it. No modification to Drupal is required.
Stand-Alone PHP
Occasionally, you might need to write a stand-alone .php file instead of incorporating the code into a Drupal module.
When you do, be sure to keep security implications in mind.
Suppose, when you were testing your web site, you wrote some quick and dirty code to insert users into the database so you
could test performance with many users. Perhaps you called it testing.php and put it at the root of your Drupal site, next to
index.php. Then you bookmarked it in your browser, and every time you wanted a fresh user table, you selected the bookmark:
<?php
/**
* This script generates users for testing purposes.
*/
// These lines are all that is needed to have full
// access to Drupal's functionality.
include_once 'includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
db_delete('users')->condition('uid', '1', '>')->execute();
for ($i = 2; $i <= 5000; $i++) {
$name = $i;
$pass = md5(user_password());
$mail = $name .'@localhost';
$status = 1;
db_insert('users')->fields(array('name' => $name, 'pass' => $pass, 'mail' => $mail, 'status'É
=> $status, 'created' => time(), 'access' => time())),->execute();}
print t('Users have been created.');
That’s useful for testing, but imagine what would happen if you forgot that the script was there and the script made it onto your
production site! Anyone who found the URL to your script (http://example.com/testing.php) could delete your users with asingle
request. That’s why it’s important, even in quick one-off scripts, to include a security check, as follows:
<?php
/**
* This script generates users for testing purposes.
*/
// These lines are all that is needed to have full
// access to Drupal's functionality.
include_once 'includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
// security check; only the site administrator may execute global $user;
if ($user->uid != 1) {
print t('Not authorized.'); exit();
}
db_delete('users')->condition('uid', '1', '>')->execute();
for ($i = 2; $i <= 10; $i++) {
$name = $i;
$pass = md5(user_password());
$mail = $name .'@localhost';
$status = 1;
db_insert('users')->fields(array('name' => $name, 'pass' => $pass, 'mail' => $mail, 'status'É
=> $status, 'created' => time(), 'access' => time())),->execute();
}
print t('Users have been created.');
Here are two take-home lessons:
• Write security checking even into quickly written scripts, preferably working from a template that includes the necessary code.
• Remember that an important part of deployment is to remove or disable testing code.
AJAX Security, a.k.a. Request Replay Attack
The main thing to remember about security in connection with AJAX capabilities such as jQuery is that although you usually develop the server side of the AJAX under the assumption that it will be called from JavaScript, there’s nothing to prevent a malicious
user from making AJAX calls directly (e.g., from command-line tools like curl or wget, or even just by typing the URL into
a web browser). Be sure to test your code from both positions.
Form API Security
One of the benefits of using the form API is that much of the security is handled for you. For example, Drupal checks to make
sure that the value the user chose from a drop-down selection field was actually a choice that Drupal presented. The form API uses
a set sequence of events, such as form building, validation, and execution. You should not use user input before the validation phase because, well, it hasn’t been validated. For example, if you’re using a value from $_POST, you have no guarantee that the user
hasn’tmanipulated that value. Also, use the #value element to pass information along in the form instead of using hidden fields
whenever possible, as malicious users can manipulate hidden fields but have no access to #value elements.
Any user-submitted data that is used to build a form must be properly sanitized like any other user- submitted data, as in the
following example. Unsafe:
$form['foo'] = array( '#type' => 'textfield','#title' => $node->title, // XSS vulnerability!
'#description' => 'Teaser is: '. $node->teaser, // XSS vulnerability!
'#default_value' => check_plain($node->title), // Unnecessary.);
Safe:
$form['foo'] = array( '#type' => 'textfield','#title' => check_plain($node->title),
'#description' => t('Teaser is: @teaser', array('@teaser' => $node->teaser)), '#default_value' => $node->title,);
It is not necessary to run the default value through check_plain() because the theme function for the form element type (in this
case, theme_textfield() in includes/form.inc) does that.
See Chapter 11 for more about the form API.
Protecting the Superuser Account
The easiest way to obtain credentials for a Drupal web site is probably to call a naïve secretary somewhere and say, “Hi, this is
Joe. <Insert small talk here.> I’m with the computer support team, and we’re having some problems with the web site. What is
theusername and password you usually log in with?” Sadly, many people will simply give out such information when asked. While
technology can help, user education is the best defense against such attacks. This is why it is a good idea to never assign user 1
(the superuser) to anyone as a matter of course. Instead, each person who will be maintaining a web site should be given only the permissions needed to perform the tasks for which he or she isauthorized. That way, if a security breach happens, damage may be contained.
Summary
After reading this chapter, you should know
• That you should never, ever trust input from the user.
• How you can transform user input to make it safe for display.
• How to avoid XSS attacks.
• How to avoid SQL injection attacks.
• How to write code that respects node access modules.
• How to avoid CSRF attacks.
• How Drupal protects uploaded files.
• How to avoid e-mail header injections.
Không có nhận xét nào:
Đăng nhận xét