Secure Coding Principles

Reference OWASP Secure Coding Principles

1. Minimize attack surface area

Every feature that is added to an application adds a certain amount of risk to the overall application. The aim for secure development is to reduce the overall risk by reducing the attack surface area

For example, a web application implements online help with a search function. The search function may be vulnerable to SQL injection attacks. If the help feature was limited to authorized users, the attack likelihood is reduced. If the help feature’s search function was gated through centralized data validation routines, the ability to perform SQL injection is dramatically reduced. However, if the help feature was re-written to eliminate the search function (through better user interface, for example), this almost eliminates the attack surface area, even if the help feature was available to the Internet at large.

2. Establish secure defaults

There are many ways to deliver an “out of the box” experience for users. However, by default, the experience should be secure, and it should be up to the user to reduce their security – if they are allowed.

For example, by default, password aging and complexity should be enabled. Users might be allowed to turn these two features off to simplify the usage of the application and increase their risk

3. Least privilege

The principle of least privilege recommends that accounts have the least amount of privilege required to perform their business processes. This encompasses user rights, resource permissions such as CPU limits, memory, network, and file system permissions

For example, multitier architecture is generally used by web application, for API tier there are different API servers for different purposes. If the caller just only calls APIs hosted on one server, then access to other API servers should be denied. In an online shopping application, the frontend used by public user shouldn’t be allowed to access account reconciliation API, which can only be accessed by finance frontend.

4. Defense in depth

The principle of defense in depth suggests that where one control would be reasonable, more controls that approach risks in different fashions are better. Controls, when used in depth, can make severe vulnerabilities extraordinarily difficult to exploit and thus unlikely to occur.

With secure coding, this may take the form of tier-based validation, centralized auditing controls, and requiring users to be logged on all pages.

For example, a flawed administrative interface is unlikely to be vulnerable to anonymous attack if it correctly gates access to production management networks, checks for administrative user authorization, and logs all access.

5. Fail securely

Applications regularly fail to process transactions for many reasons. How they fail can determine if an application is secure or not.

isAdmin = true;
try 
{   
    codeWhichMayFail();   
    isAdmin = isUserInRole("Administrator");
} 
catch(Exception e) 
{   
    log.write(e.toString()); 
}

The user will be assigned administrator permission if there is exception occurs in try block, obviously this is a security risk. The appropriate way should be this:

isAdmin = false; 
try 
{   
    codeWhichMayFail();   
    isAdmin = isUserInRole(“Administrator”); 
} 
catch(Exception e) 
{   
    log.write(e.toString()); 
}

6. Don’t trust services

Many organizations utilize the processing capabilities of third party partners, who more than likely have differing security policies and posture than you. It is unlikely that you can influence or control any external third party, whether they are home users or major suppliers or partners.

Therefore, implicit trust of externally run systems is not warranted. All external systems should be treated in a similar fashion.

For example, a loyalty program provider provides data that is used by Internet Banking, providing the number of reward points and a small list of potential redemption items. However, the data should be checked to ensure that it is safe to display to end users, and that the reward points are a positive number, and not improbably large.

7. Separation of duties

A key fraud control is separation of duties.

For example, in an application certain roles have different levels of trust than normal users, especially for administrators, in general, administrators should not be users of the application. An administrator should be able to turn the system on or off, set password policy but shouldn’t be able to log on to the storefront as a super privileged user, such as being able to “buy” goods on behalf of other users.

8. Avoid security by obscurity

Security through obscurity is a weak security control, and nearly always fails when it is the only control. This is not to say that keeping secrets is a bad idea, but that the design or logic of the security control should be based on open and known principles.

For example, in a cryptographic system, the algorithm can be exposed publicly, only the key should be required to remain secret. It should not rely on keeping the algorithm secret in order to remain secure, because the short/simple encryption key will make you system insecure.

Another example, the security of an application should not rely upon knowledge of the source code being kept secret. The security should rely upon many other factors, including reasonable password policies, defense in depth, business transaction limits, solid network architecture, and fraud and audit controls.

A practical example is Linux. Linux’s source code is widely available, and yet when properly secured, Linux is a hardy, secure and robust operating system.

9. Keep security simple

Attack surface area and simplicity go hand in hand. Certain software engineering fads prefer overly complex approaches to what would otherwise be relatively straightforward and simple code.

Developers should avoid the use of double negatives and complex architectures when a simpler approach would be faster and simpler.

For example, for some special reason in a project we are unable to use http session, when user uses the system it is unacceptable to input username and password for each page. Most people will think we can store username and password on client side, and encrypt password by some algorithm. However this causes more troubles, such as how to store/update algorithm secret, whether the different secrets should be used for different users. If user changes his/her password the new password have to be synchronized between server and client. If password on client side is exposed the password rule will be known by others. The better way is storing some authentication token on client side by cookie mechanism, the token is created when user signs in the system and can be refreshed and will be expired after some period. Thus the implementation on client side is simplified, all is controlled by server. Also in the future it is easy to switch to http session.

10. Fix security issues correctly

Once a security issue has been identified, it is important to understand the root cause of the issue and develop appropriate test cases for it. Make sure no new issues are introduced by the fix.

For example, in one application MD5 algorithm is used to create hash value of password,the application is put online and after some time the database data is exposed by attack, development team fixes the issue quickly and change algorithm to SHA2, however the test cases just cover general business scenarios and miss the rare case for data restore. As a result in disaster the all users fail to sign in system after backup data is restored. The root cause is the hash value in backup data is created with MD5 but the application has switched to SHA2, so it always fails.