Critical Security Areas That Software Engineers Have To Know To Secure Their Solutions
Vulcanus
Posted on January 1, 2022
Societies in industrialized countries depend more and more on software. The rising impact of cyber-physical systems on the real world as well as the amount of personally identifiable information collected and processed shows that systems must be secure. This is not easy to achieve due to rising complexity of those systems.
Organizations have information security departments that support securing business functions and train employees in a variety of security topics to show how to react on certain events and how to handle classified information. While there are trainings for administrators to show them how to secure a system, often little to none effort is put into teaching software engineers and developers on how to develop a secure software solution. This is quite a big issue I'd like to address and raise awareness about.
The Open Web Application Security Project (OWASP) created the "OWASP TOP 10 Proactive Controls project (OPC)" to encourage developers starting with application security. This blog entry summarizes the content of it and adds hints and information to it too. Please keep in mind that this should only raise awareness and is a starting point to help get deeper into this topic.
Define Security Requirements
Security falls under the category non-functional requirements. It should define the needed security functionality the software has to satisfy. To save up time and not re-invent the wheel on each new project, you can select security requirements from a catalog. There is a general one called "Application Security Verification Standard (ASVS)" and one for the mobile named "Mobile Application Verification Standard (MASVS)". They contain a collection of requirements which are best practices for each listed category. Fortunately they have mapped those requirements with CWE (common weakness enumeration which is basically a list of software and hardware weaknesses). Depending on the used tools, those CWEs can be automatically scanned in your code.
Leverage Security Frameworks and Libraries
Normally third-party libraries/frameworks are included into the project to re-use already written code. You should only use those from trusted sources, which are actively maintained and used by many applications. Keep them up to date and encapsulate the library, so you only expose the required need into your software. There are quite some dependency checkers out there to help you select dependencies and keep them up to date.
Secure Database Access
This has some subitems, but we'll go through them quite quickly. Secure your queries. We all know those funny SQL injection jokes, but you can solve this problem quite easily with query parameterization. You can find a cheat sheet from OWASP here and another one from Bobby Tables here.
Database management systems are not always "secure by default" configured. There are guidelines and benchmarks available out there which you should check out like here.
Access to the database should be properly authenticated. This should take place over a secure channel, and your credentials should be properly secured. Besides authenticating with credentials, you should also check out if it's possible to access it instead with your managed identity.
The last point is secure communication. Encrypting your data in transit by having a end-to-end communication security when sensitive data is transmitted over any network. This can be done via TLS. There are guides there helping you choose the minimum allowed TLS version and choosing a cipher suite.
Encode And Escape Data
And here we reach injection attacks again. By encoding characters we ensure that special characters are not processed for malicious intends. This means that the content will be displayed but not executed. For example, instead of sending "<script>"
we encode special characters inputted by the user and send "<script>"
which will be displayed on the browser like "<script>"
but will not be executed. This output encoding should be applied before the content is passed to the target interpreter, to defend against XSS. There are some examples for C#, Java and PHP. There are also other types of encoding and injection defenses like "shell escaping" for os command input. Forms of escaping are not limited to those examples listed here. Look for guidelines and best practices when using user input for certain operations.
Validate All Inputs
You might have already noticed that you can't trust input from clients which means that the data has to be validated before usage.
Data should be checked that its syntax and semantic is valid.
Syntax validity ensures that data are in a expected form and should not allow any deviations. If three digits are expected, it should be checked that the input consists only of digits and has three digits in length.
Semantic validity accepts input only in an acceptable range specified by the applications functionality and its context. For example a start date has to be before an end date.
Those validations can be performed both on the client and server side, but security related validations always have to be done on the server side, since the validation on client side can be bypassed.
There are in general two approaches for syntax validation: blacklisting and whitelisting. While blacklisting blocks exact texts (e.g. "<script>"
) to prevent injections, is whitelisting checking for data set matches. In general it is recommended to use whitelisting on a minimal approach, instead of blacklisting since it is prone to bypasses. In other words, whitelisting limits attack surface, while blacklisting detects and stops obvious attacks.
Bear in mind that those validations have limits. Since complexity allows more variations and posibilities, valid data cann still be dangerous.
Another way to check whether data matches a specific pattern is the usage of regular expressions. This should be used with caution since expressions can get quite complex as well as hard to maintain. It also enables a regular expression denial of service attack (ReDOS) which produces a denial of service due to the exploitation of the exponential time worst-case scenario.
There are plenty validation libraries that can be leveraged to validate data. PHP has filter functions, and Java has the Hibernate Validator and C# the FluentValidation. You can also sanitize your data to erase not needed data in your input. Please keep in mind that input validation should not be your primary method to prevent injections and other attacks.
Another problem you might encounter is the validation of serialized data. Avoid deserializing data from untrusted sources. If this is not possible, you might want to implement integrity checks or encryption to prevent tampering. Enforce strict type constraints and possibly run code in a low privilege environment like in a temporary container to deserialize data. Log exceptions and failures such as the not expecting incoming type or failure in deserialization.
Apart from serialized data, there is also the problem with autobinding. Some frameworks support automatic binding of HTTP request parameters to server-side objects consumed by the application. Those bindings enable an attack vector to exploit a vulnerability called "Mass assignment". For example the user can set a parameter like "isAdmin" to true to elevate privileges. There are two ways to handle this, by either avoid autobinding and use Data Transfer Objects (TDOs) which are basically POCOs, or setup whitelist rules to define which fields are allowed to be auto-bound. There is a cheat sheet by OWASP you might to check out here to get more information on how to resolve this issue.
Enforce Access Controls
Access Controls manages the access to systems as well as resources and ensures that only authorized users/systems have access. It should be forced that all requests go through the access control to ensure that every request is checked and authorized to pass. You may already come across of the terms privilege, right and permission. Those are not interchangeable terms! For more information, read Wentz Wus article.
This topic should be thoroughly designed up front and taken early into account in the designing phase.
There are different types of access controls that should be considered (but not limited to):
- Discretionary Access Control (DAC): lets people manage content by their own.
- Mandatory Access Control (MAC): restricts access based on sensitivity (by a label) of the information and the authorization of the user to access such sensitivity.
- Role Based Access Control (RBAC): controls access to resources based on defined rules. Performed actions are identified with roles rather than with individual subject identities.
- Attribute Based Access Control (ABAC) manages the request based on policies which combines attributes of the user and object.
There are two principles that you should embed when using access controls. The first is deny by default. This means that if a request is not specifically allowed it must be denied. The second is the principle of least privilege. Ensure that only the least and only necessary access is possible. Often when time passes, it can happen that privilege creep occurs. This means that an identity accumulated access rights and has higher privileges than necessary, so keep that in mind and check regularly if certain permissions and rights are needed.
Linked to a granular access control is the programmed check of permissions. Many applications use access controls that are role based. This limits the developers and adds dangers to it.
if(user.hasRole("SuperUser") || user.hasRole("Admin"))
{
doAction();
}
Instead your should implement rights and check for them.
if(user.hasRight("RightX"))
{
doAction();
}
With this, you don't need to re-deploy everything if new roles are added that should have the privilege to perform actions and it is easier to maintain. It also enables for a more granular access control which helps administrators to configure the system more securely.
Furthermore, you should ensure that all access control failures should be logged to ensure non-repudiation.
Data Protection
Data can contain sensitive information which requires more protection, since it may fall under laws and regulations. It is important to classify data in your system to determine sensitivity. Depending on those classifications it may also add security requirements to the system/infrastructure that collects, processes or stores this data.
Data can be in three states. At rest, in transit or in use. Depending on the classification you have to secure the data in each state to avoid information disclosure.
Application secrets should never be stored in code, configs or other files. Keep them in a secret vault like Azure KeyVault or Amazon KMS. Besides security this also gives more flexibility on configuring your solution.
I would like to add here to also think about data retention as well as backup strategies. For data retention you have to keep laws and regulations in mind. Consult with a specialist or lawyer to know what the requirements are.
Backup strategies should not only be planned and executed, but also the results tested. There is a saying that you do not have a backup when you did not test it. In the end, you do not want to be on several tech blogs because of some deletion incident and backup failure like GitLab.
Implement Security Logging and Monitoring
The concept of Security logging is to log security information during runtime. It can be used for forensic analysis and investigations, as well as satisfying regulatory compliance requests. Monitoring is the live review of logs using various forms of automation.
Logging solutions must be designed, build and managed in a secure way. Encode and validate dangerous characters before logging to prevent injections or forging attacks. Ensure log integrity to protect against tampering. When logging, remove any sensitive information to avoid information disclosure. There should also be a common logging format to be consistent. Keep also an eye on time syncing across systems to have consistent timestamps in your logs. Furthermore, you should forward logs to a central, secure logging service to allow centralized monitoring and securing log data.
To identify potentially malicious activties, following activies can be logged as high severity:
- Submitted data that is outside of an expected range
- Submitted data that contains changes that should not be modifiable
- Requests violating access control rules
For a more comprehensive list, check out OWASPs AppSensor Detection_points tab here.
When encountering those activties, the application should respond to possible attacks and shut them down.
There is also a small cheat sheet related to application security logging here.
Handle all Errors and Exceptions
Exceptions can happen in various ways and should be handled accordingly. This handling occurs in all areas of the application including business logic and security features. It is also important for intrusion detection. Certain attacks against the application may trigger errors which can help detect attacks in progress.
Manage exceptions in a centralized manner to avoid duplicated try/catch blocks and ensure all unexpected behavior is correctly handled. When displaying the error message to the user, be sure that you do not leak any critical information but still provide enough information to respond properly.
When logging error messages, provide enough information so that the support, forensics and incident response teams understand the problem.
To help discover possible failures early, you can also use Netflix's Chaos Monkey. It randomly terminates VMs and containers to show how resilient your services are.
Some more Information
Code Review
Besides the mentioned areas, you should also have a look at OWASP's Code Review Guide. It is quite comprehensive but also raises awareness on different topics, containing code examples and figures to illustrate in an easy way to show what has to be considered when reviewing code besides clean code practices and business logic.
Do not write your own encryption
Some developers are tempted to rollout their own encryption. Schneir's Law states
Anyone, from the most clueless amateur to the best cryptographer, can create an algorithm that he himself can't break.
Since those persons believe that they can not break their cipher, they use it as evidence that it is unbreakable. This is a good example of the Dunning-Kruger effect. There is a discussion on why it is discouraged to write your own encryption on Stack Exchange.
Therefore use existing solutions that are recognized by the industry and follow their best practices.
Posted on January 1, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 1, 2022