Welcome to another edition of Security Corner. This month's topic is secure design, the application architecture that provides the foundation for secure development. The column on input filtering touched on this topic a bit, and it's something that is sure to appear in this column again.
Design has always been a controversial topic, but only because developers tend to be very loyal to their own discoveries, ideas, and approaches. Thus, discussing software design can spawn debates rivaled only by coding standards discussions, text editor opinions, and programming language choices. With this in mind, please feel free to suggest different approaches than the ones written here. Like any other developer, I'm always interested in learning new approaches, and I'd be happy to do a few case studies of any particularly sound designs.
In order to demonstrate some common approaches, I describe two different overall methods of organizing your applications that I refer to as the dispatch method and the include method.
A good software design should help developers to ensure that input filtering cannot be bypassed, ensure that tainted data cannot be mistaken for filtered data, and help identify the origin of data. Without these characteristics, a developer's task is more difficult and error-prone. Just as complexity in an application leads to more bugs, the lack of a sound design leads to more security vulnerabilities.
One popular design that embodies each of these characteristics is the dispatch method. The approach is to have a single PHP script available to the public (via URL). Everything else is a module included with
require as needed. This method usually requires that a GET variable be passed along with every URL, identifying the task, although modern frameworks and techniques eliminate this need. This GET variable can be considered the replacement for the script name that would be used in a more simplistic design. For example:
dispatch.php is the only file within document root. This allows a developer to do two important things:
- Implement some global security measures at the top of
dispatch.phpand be assured that these measures cannot be bypassed
- Easily see that input filtering takes place when necessary, by focusing on the control flow of a specific task
I have developed applications using this approach with great success. As a developer, I especially appreciate the simplicity. Over-architected applications tend to solve problems that don't exist, and the added complexity is rarely worth it.
To further illustrate this approach, consider the example
dispatch.php script given in Listing 1.
Keeping in mind that
dispatch.php is the only resource available to the public, it should be clear that the design of this application ensures that any global security measures taken at the top cannot be bypassed. It also lets a developer easily see the control flow for a specific task. For example, instead of glancing through a lot of code, it is easy to see that
end.inc is only displayed to a user when
TRUE, and because it is initialized as
FALSE just before
process.inc is included, it is clear that the logic within
process.inc must set it to
TRUE, otherwise the form is displayed again (presumably with appropriate error messages). It is also impossible to access
end.inc otherwise, because it is not available via URL (it is not within document root).
In order to keep
dispatch.php as simple as possible, I recommend only adding logic that is important to the control flow of the application and putting everything else in modules. It can also help your module organization to categorize them in three categories: presentation, logic, and database queries. These two guidelines make
dispatch.php very readable.
If you use a directory index file such as
index.php (instead of
dispatch.php), you can use URLs such as
You can also use the Apache
ForceType directive or
mod_rewrite to accommodate URLs such as
An almost opposite approach is to have a single module that is included at the top of every public script (those within document root). This module is responsible for all global security measures, such as ensuring that input filtering cannot be missed.
Listing 2 gives a simplistic example of such a script,
security.inc. This example demonstrates the handling of form submissions, although this is only one example of the types of tasks that can be performed here.
process.inc handles input filtering, and
security.inc makes sure that it always executes when a form meets the minimum criteria for testing, which is that it only contains expected data. This is done by adding a hidden form variable to every form that identifies it (or using any approach that can be used to distinguish forms) and then comparing form fields with what is expected.
Listing 3 shows an example of a form that identifies itself as login and adheres to the checks from the example
security.inc script shown in Listing 2.
auto_prepend_file directive to ensure that
security.inc is not accidentally left out.
A topic worth reiterating here is naming conventions. However you decide to name your variables, make sure that you choose a method that will not make it easy to mistakenly use tainted data. One approach is to rename any variable that is filtered to something that distinguishes it as being clean.
For example, Listing 4 demonstrates testing the format of an email address. The variable
$clean['email'] will either not exist, or it will contain a valid email address. With this approach, you can safely use any variable within the
$clean array in your programming logic, and the worst-case scenario is that you reference an undefined variable. You can catch these types of errors with your error reporting (a future topic for Security Corner), and the impact is much less severe anyway.
If you place your input filtering in a separate module (such as
process.inc mentioned in Listing 2), it is important to initialize your
$clean array in the parent script and to be sure that no path through your logic bypasses this initialization.
Until Next Time...
Of the two approaches discussed, I prefer the dispatch method. The main reason for my preference is that it levers existing mechanisms that have been proven reliable, such as the fact that only files within document root can be accessed via URL. Another benefit is that it relies less on the developer remembering to do something for security.
Again, if you have a particularly secure design that you wouldn't mind sharing with your fellow readers, please let me know. I'll be happy to have a look and provide a thorough analysis in the hopes that everyone benefits.
You should now have the tools you need to add security precautions to your next application design. Just be sure that your approach satisfies the three important characteristics I mentioned earlier. Help developers to ensure that data filtering cannot be bypassed, ensure that tainted data cannot be mistaken for clean data, and identify the origin of data.
Until next month, be safe.