pst.libre.lu

secure programming - good practices
26/02/2005

toc

1. Introduction

Secure coding is not simply getting behind it's keyboard and hack, it is real engineering. Why do bridges support trains for hundreds of years ? How could an Eiffel Tower swing several meters sideways in the wind and still welcome thousands of visitors per day, without harm ? Well engineering is taken serious. First there is an architectural model, which will provide design plans (blueprints), only then construction can start.

Why shouldn't we adopt this procedure too ? Well we should !

2. Architecture

A security architecture is the process of selecting design elements and principles to match a defined security need. This implies to know how secure the program should become !

A good security architecture can be applied many times, to many applications and should serve as a framework for secure design decisions. A good advice is to work with an architectural document, where the different ascpects are laid down.

2.1. Architectural Document

2.2. Principles of security architecture

The 30 basic principles of security architecture are:

2.2.1. Ask questions

About what ? Well here are some basic topics to ask questions and get answers before continuing the architecture process:

2.2.2. Focus before leaping

It is important to know the destination before stepping on the gas. Engineers are problem solvers by nature, so first the problem should be well defined as well as the goals to reach.

2.2.3. Define "just secure enough"

The idea is not to make an appplication as secure as possible, but just secure enough. Ressources (of any type) are expensive so a precise definition on how secure an application should be, that's approriate to the (business) context (it's environment), is crucial.

2.2.4. Do engineering not prototyping

Employing standard engineering/construction techniques for software is critical for a good development process. Good security requires good design and good design techniques. This can avoid factors like :

2.2.5. Identify assumptions

Good engineers have in common the ability to look objectively at elements like mental models, system ressources and process interruptebility and suspension. This enables clear identification of assumptions to be handled correctly.

E.g. The users of this application will be human beings.

2.2.6. Get security in from day one

Research reveals that fixing a security bug in the design phase costs 1/60th of the cost for the same bug to be patched after the release. Planning with this in mind does save costs, maintenance and as such security of the application.

2.2.7. Design with the enemy in mind

This is the adversary principle. It's important to try to anticipate how an attacker might approach solving the security puzzle of a speficic application. This implies knowing a little of the usage environment of the application, to identify potential ennemies.

2.2.8. Work with the chain of trust

The chain of trust must be understood and respected, it's not save to invoke untrusted programs from within trusted ones. Secure applications should always validate what is presented to it, should not pass tasks to less-trusted entities and only emit valid and safe information.

2.2.9. Be stingy with privileges

Sometimes called principle of least privilege, the idea is to limit privileges to it's uther most needed.

2.2.10. Always test against policy

Every attempted action must be tested against policy to get a stringent security, this is also called the principle of complete meditation.

2.2.11. Build in fault tolerance

To build appropriate levels of fault tolerance, the 3 Rs, from the CERT-CC, come in the play:

2.2.12. Address appropriate error-handling

This should indeed be considered at every stage of a software's lifecycle:

Architect
Adopt a general plan for error handling, like stop on bizarre (unexpected) ones and log on all others.
Designer
Devise rules on how to detect, discriminate and respond to errors.
Coder
Capture the triggers and implement the design.
Operator
Needs to check processes and if error handling is efficently done.

2.2.13. Degrade gracefully

Graceful degradation is the fact to continue operating in a restricted or degraded way, if something fishy happens, instead of simply stopping or failing.

2.2.14. Fail safely

What should be the default fail behavior of a given application ?

2.2.15. Choose safe defaults

The "fail-safe" argument from before is somehow a sub-element of this: the need to provide safe default actions in general.

2.2.16. KISS (Keep It Simple Stupid)

Simple systems are easier to design, implement and test well, moreover, features that don't exist can't be a security risk, nor can have bugs.

"A good theory should be as simple as possible - but not simpler." Albert Einstein

2.2.17. Modularize

Modularize thoroughly and fully.

2.2.18. Don't rely on obfuscation

Security through obscurity doesn't work ! Concealing how something works (like encryption algorithms) is in no sense an advantage, but can, in contrary, be dangerous. Security should be intrinsic.

2.2.19. Seek statelessness

By state we think of the information a program retains while a transaction (or command) is executed. If a program retains minimal state, it's harder for it to get into a confused, disallowed state.

2.2.20. Strive for practical measures and useability

In theory there should be no differene between theory and practice, but in practice, there is. That's why it's important to create a useable user environment (GUI, etc.) that makes it easy to do the right thing. This is also called the principle of psychological acceptability.

2.2.21. Make accountability always possible

A good architecture must ensure that every action, as well as the responsibility of the assets, can be attribute to a specific individual.

2.2.22. Limit resources consumption

A gentle ressource usage contributes to the overall security of a system. Strange or seldom tested exceptions are often only detected when reaching the limits of resources exhaustion.

However, resource-consumption limitation, must also be combined with meaningful error recovery and handling to be really effective.

2.2.23. Make event-reconstruction possible

It must be possible to track the exact sequence of events of key actions. This implies keeping audit-logs, it's the principle of auditability.

2.2.24. Eliminate "weak links"

"A chain is only as strong as it's weakest link." Try to eliminate them. This implies plannig the security as a whole, addressing the entire range of elements of a specific application.

2.2.25. Use multiple layers of defense

In depth defense is the key, wear a belt and suspenders !

2.2.26. View things in it's holistic whole

This goes even farer than point 2.2.24, it's not enough to create a secure application by it's own. All it's interactions with the outside world must also be considered and from design on.

2.2.27. Reuse secure code

Don't reinvent the wheel ! Make use of code, libraries or whole programs, that are known to be secure. It might even be interesting to have "code reuse" clauses in the policies.

2.2.28. Don't rely on off-the-shelf software

However, despite the previous section, code reuse is not to be taken an easy task. For the sake of security issues off-the-shelf programs have to be treated very carefully, especially for mission critical operations. Make sure to assess the security aspects of any solution intended for in-house usage.

2.2.29. Don't forget democratic principles

Security should stay security. Don't implement security measures that mistreat individual privacy rights. In most countries there is a legal framework for this, regard it.

2.2.30. What did I forget ?

Always ask yourself this question before moving along, humans forget things !

3. Design

Good design is the basis for an efficient software development process, as it not only enables to build a good defensive basis into the software from the begining, but provides safe foundations for future extensions and maintenance.

Secure design has to be elaborated thoroughly, the following steps being typical efforts to perform.

3.1. Risk assessment

Before starting to protect, one has to know and understand what is to be protected and from whom. This process is commonly called a risk analysis, trying to determine the threats, the vulnerabilities and their impacts.

It probably sounds a little strange to perform a risk analysis for the sole purpose of developing an application, as such an analysis normally includes the whole organisational aspects as well. Well that's exactly what is seeked. The aim is to define all the implications the new element will have on the whole and vice-versa.

3.1.1. The models

Despite the specific method chosen, be it OCTAVE, NIST SP800-30, EBIOS, ..., the folowing essential points should be addressed:

3.2. Risk mitigation

After the risks were assessed it is time to think about the management of those risks. Risk is good : Having risks is a sign that one's still in business, managing risks is a good way to stay in business.

3.2.1. The options

Again dependant on the specific method chosen, this process might diverge slightly. Generaly the options of risk management are:

3.3. Work with a mental model

This practice might somehow look strange, but it is a nice way to get a global picture of the design. Trying to build a metaphor of the future application, can already show some potential issues and propose solutions. Again, Albert Einstein, with his famous "Gedankenexperimente" can be taken as a prominent example of the effectiveness of such an approach.

3.4. Define high-level techniques

Now the more technical work can begin. Technical issues can be divided in 3 categories:

3.5. Choose appropriate measures

It's not enough to cover, threats, vulnerabilities and attacks, implementation means more. Detailed and carefuly choosen measures are the key for success. Various good practices exist, here some thoughts to consider.

3.5.1. Background factors

Get the "big picture" of the environment and it's customs.

3.5.2. Business issues

Address corporate culture and cost issues, important factors are:

3.5.3. Cost-benefit analysis

Implementing security measures is, of course, a crucial element, however, as with so many things in live, the cost/benefit ratio has to be coherent.

Pay attention to include all costs:

To determine benefits is harder, use the detailed risk analysis from before to get the picture.

3.5.4. Methodologies

With large or long-term projects it is often difficult to keep all the information (collected until now) consistent. In that case a more stringent method can be used to rationalize the approach. This has the following advantages:

3.6. Evaluate the process

Far less evident yet, is the principle of repeatable results. The defined processes for security design decisions should yield consistent, coherent and reproduceable results.

3.7. Some special design issues

Security design is an important part of software engineering, and the above practices cover the global picture well, or ? Well what about software you didn't design, third-party libraries, existing applications, that have to be secured ? The following three issues are good design practices for those kind of special issues.

3.7.1. Retrofitting

Without access to the source code one isn't powerless, several significant security techniques exist to "retrofit" such applications:

3.7.2. Maintenance

Existing applications, for which access to source code is granted, needing a security maintenance to be up-to-date can be another source of vulnerabilities. Considering the following few advices can help to keep the global security safe.

3.7.3. Compartmentalization

This concept approaches security issues the way that it places untrustworthy users, programs, objects in a kind of virtual box so that they can't do any harm. The three most compartmentalization techniques are:

4. Implemetation

Unfortunately (already known since Morris's Internet Worm in early 1988) the most common implementation flaw is still the buffer overflow. Here some good implementation practices to fight them and others (of course):

4.1. Inform yourself

This might sound implicit, but it's always good to be reminded about.

4.2. Handle data with care

Verify carefuly every piece of data input. Ways to do this includes practices like:

4.3. Reuse code

It always makes sense to reuse software or pieces of code that has been thoroughly reviewed and tested, and has withstood the tests of time and users.

4.4. Thoroughly review

Some common review techniques:

4.5. Use checklists

As stated before checklists are a good way to be sure to cover everything necessary (we're just humans). Some basic entries a good checklist should contain, are:

4.6. Create maintainable code

In correspondence with the Maintenance section from the design chapter, where "we" were to maintain existing code. Here it's "our" job to be kind to the maintainers. For that task the following practices are useful:

5. Operation

Traditionally in many companies, the development staff and the operational staff are seperate and even sometimes thorough competitors. As should have become clear till know this is a very bad approach. Development and operations are two sides of the same coin.

Security is everybody's problem !

The operational level security measures can be seen as a layered system of practices.

5.1. Harden the network

Security most of the time begins on the network level, but one should not stop here.

5.2. Secure the OS

The network and OS are the foundations on which the applications are build on, consider them deeply.

5.3. Deploy carefully

Now, with the network and the OS secured, comes the application.

5.4. Define sound operations practices

5.5. Finally ...

... keep in mind that security measures are a process not a one time work. Applications decay, by itself or via environment changes, maintain them !

6. Testing

About automation and testing, some useful automation tools to test the finalized applications.

Bibliography

pdf version