This is the multi-page printable view of this section.
Click here to print.
Return to the regular view of this page.
Develop
It's not possible to provide a recipe on how to write secure code in a few simple and concise articles. However, it doesn't mean it's an insurmountable task reserved only for security experts! Everyone developing software at Bouvet should be familiar with OWASP Top 10, which is a good starting point to understand the challenges faced by developers.
The articles you find under the topic Utvikle on this page will focus on what happens on a developer's workstation, apart from actual code. They won't delve into specific attack methods or how to defend against them. You will also find advice on how a team should work to prevent, identify, and fix vulnerable code.
1 - Development Environment, Tools, and Build Environment
The environments and tools we work with are essential for the project, and it is important that we have control over which tools are used and how they are configured.
The development environment and build environment are some of the most important aspects of a development project, and it is crucial that we have control over these dependencies.
There are many different ways to approach these environments; some develop and build locally on their own laptop, while others use a combination of dedicated development and build environments - based on cloud services like Azure DevBox or on-prem development servers.
Regardless of the solution chosen, there are some important points that should be documented and maintained - both to have a basis that can be threat modeled and validated, but also so that new team members can quickly onboard and get a “right” environment.
Developers often have different preferences regarding the use of IDEs and extensions, and many have likely created a workflow that works for them. Remember that all tools must be kept up to date, and each new tool introduced increases the attack surface, especially when it comes to using extensions, such as those from Visual Studio Marketplace and similar sources.
Development Environment and Build Environment
One of the major risk elements in all development is if
- unauthorized persons can access a machine where development or building is taking place
- the machine can access the internet without significant restrictions
- such machines lack monitoring
All developers who write code today have many dependencies on libraries and packages fetched from open-source ecosystems. Few of these have any relationship to security, and as we have seen time and again, malicious actors can take over such components or create their own copies under similar names. Such packages represent a significant risk as they can introduce backdoors that open up for unauthorized access or exfiltrate data from the machine to an external recipient.
Important measures to mitigate risk include:
- blocking all incoming network traffic
- blocking all outgoing network traffic
- only opening up the accesses that are absolutely necessary
- avoiding the use of general machines for internet browsing, office activities, and similar for development
It is clear that one cannot always justify setting up dedicated machines used only for development based on cost/benefit, but this should be considered on a case-by-case basis to ensure that one is aware of how this choice affects the risk profile of the project.
2 - Secrets
All development projects require secrets such as connection strings, identities, passwords, certificates, and much more. These must be stored securely, and we must ensure that we use them in a secure context with the right support tools and processes.
Secrets in development projects are represented as connection strings, passwords, keys, certificates, and any other sensitive information we do not want others to know about.
There are many different approaches to handling these, and this has evolved as new services emerge. For cloud solutions, services like Azure Key Vault and similar have almost become standard, as these services handle multiple aspects of how to use and manage secrets.
Basic Principles for Handling Secrets
- Secrets should never be hard-coded or checked into version control systems
- Verify compliance by scanning the code - reject commits with secrets and rotate them immediately!
- Keep track of which secrets you have and what access they provide
- Limit the lifetime of secrets - no secrets should live more than a year, passwords and keys much shorter
- Team members should not always have access to all secrets; elevate or provide access as needed
Cryptography and Hashing
Cryptography and hashing algorithms are complex topics, and it requires significant resources and a lot of competence to build good algorithms that are secure. For this simple reason, you should never create your own, no matter how clever and secure it seems to be.
What you should do instead is:
- Familiarize yourself with best practices for your programming language, framework, and platform
- Ensure you are not using vulnerable algorithms such as SHA1, MD5, or DES
- For cryptography, understand the recommendations for key lengths and usage
Remember
Secrets should never be checked into the source code system!
3 - Data Validation
Data is an attack vector; you should never blindly trust that it is safe, regardless of the source. This also applies when retrieving data from other systems!
If you receive input from users or fetch data from other systems, you should never blindly trust it. OWASP Top 10 has listed various forms of vulnerabilities related to code injection at the top of their lists since the first one was published in 2004.
Ensure that the data contains what you expect; if you import files, they must be scanned for malware before processing them further. Also, consider the need for using sandbox environments to process such files to limit the damage potential.
Understand the threat landscape for the relevant file types. For example, it is possible to embed malware in an image; if this is not mitigated, the application may be vulnerable to this threat. Other file formats entail different types of threats that require other measures.
Also, consider using Content Security Policies to prevent data fetched from other systems but presented to the customer from resulting in a successful XSS attack.
4 - Security Practices
There is much to consider when building a secure solution, and an important starting point is the OWASP Top 10.
There are many different types of vulnerabilities and weaknesses to consider when developing new applications. The Open Worldwide Application Security Project (OWASP) has published a list of the 10 most common types of vulnerabilities since 2004, primarily aimed at web applications. Although this list does not cover everything, it is often used as a starting point for vulnerabilities that must be addressed in development projects.
Current Points on the OWASP Top 10:
- Broken Access Control
- Cryptographic Failures
- Injection
- Insecure Design
- Security Misconfiguration
- Vulnerable and Outdated Components
- Identification and Authentication Failures
- Software and Data Integrity Failures
- Security Logging and Monitoring Failures
- Server-Side Request Forgery
If the team has no processes around secure development, this would be a good start. For teams with greater maturity in application security, other checklists, such as the Application Security Verification Standard - also from OWASP, are a further option. This is divided into three different levels; level 1 covers the main points and requires only general competence, while level 3 goes more in-depth and requires specific competence and support tools.
OWASP publishes much more in addition, including other Top 10 lists and what they call "Cheatsheets"; detailed information on specific security-related topics.
5 - Software Supply Chain
Everyone who develops software uses third-party packages. All third-party packages represent code written by others and pose a risk to the delivery if we do not have control over what we use and an overview of weaknesses and risks associated with them.
When we build software, we depend on a multitude of different third-party packages from ecosystems like npmjs, nuget, PyPi, or others. This is code “written by others,” where we rely on these producing packages without major weaknesses and without malicious intent. Sources such as Gartner, Sonatype, and Snyk have estimated that as much as 90% of the code in a typical development project may consist of such third-party packages, so the risk is significant if we do not have control.
An important tool is to conduct a so-called Source Composition Analysis - SCA to get an overview of the packages we use, both direct and indirect (transient) dependencies, as well as the risks associated with them.
Security Risks
There are several security risks associated with third-party packages, but unfortunately, the ecosystems for such packages are not very proactive on the security front. Infected packages that are identified are removed, but for those using automation to keep dependencies updated to the latest version, this is often too late.
Vulnerabilities
They may contain known vulnerabilities (CVE) that can be exploited. Some of these can be mitigated by upgrading the packages to the latest version, while others have more fundamental challenges that can be mitigated in other ways. Sometimes, some CVEs are created for weaknesses that are technically a vulnerability but are ‘by design’. An example of this is the Python package Pandas which among others has the vulnerability CVE-2020-13091. This allows users to deserialize files without checking where they come from - this has been registered as a vulnerability as it could be exploited if other measures are not in place.
Malicious Code
The packages we download and use run in the same context as our code and can use the same resources and accesses it has. The content of the packages we consume is entirely beyond our control, and if one of them contains malicious software (malware), the consequences can be significant.
In recent years, there have also been examples of packages often referred to as “protestware,” as they contain logic that performs actions if users can be geolocated to specific countries. The package “peacenotwar” is one such example, which was added as a dependency to node-ipc
, a widely used package. This resulted in peacenotwar
also being downloaded by many users; those geolocated to Russia or Belarus had data deleted and were subjected to DOS attacks via the package.
Whether something is meant as malware or protestware, the consequences are serious, and all development projects should have measures in place to limit risk and consequences in case an incident occurs.
Use of CDN
Content-delivery-networks to distribute Javascript libraries have been used by many as an easy way to include these in the code without having to include them in the build or deploy process. The idea is good, but you then have a direct dependency on a source you have no control over, which can be abused to spread malware.
License Model
There are many different license models for available components; some are completely free and impose no requirements for use, while others like AGPL and GPL can have significant consequences for what you develop. Most ecosystems also allow the use of proprietary licenses, which can limit what you can use a package for without a valid license. Some have specific requirements in the license, others are free for personal use but require you to purchase a license if used commercially.
How Do We Secure Ourselves?
To protect the system against these threats, there are several effective measures we can take. Many of these are already covered elsewhere, but there are still some threats unique to external packages.
Threat Model and Complexity
In many cases, we depend on many packages that we do not use directly, so-called transitive dependencies. Each package in a solution represents an increase in complexity and attack surface, increasing the chance that something will go wrong at a later stage.

An important question everyone must ask is “do we need this package?” What is the cost of creating the functionality ourselves compared to the risk and complexity associated with adding it?
Monitoring Dependencies
An important tool is analysis tools that help us control vulnerabilities and risks in our dependencies. There are many actors on the market, ranging from simple ones like GitHub’s Advanced Security with Dependabot to more advanced ones like Snyk and Sonatype. In many cases, the data basis for the solution makes the big difference, but there are also a number of useful features to consider:
- Policy Management - Sonatype tags all packages with metadata, allowing you to define policies that dictate what can be used or not.
- Automatic Remediation - Tools like dependabot (GitHub) automatically upgrade packages when vulnerabilities are discovered, and updates are available.
- Integrations and Notifications - Many tools like Snyk SCA can integrate into IDEs and CICD.
- Risk Overview - Many solutions provide an overall view where you can see the total risk picture for individual applications or larger parts of the portfolio.
Maturity
This can be difficult to quantify, but how active is the community around a package? Is it maintained by individuals, groups of developers, or does it have economic or other support from a company?
How likely is it that the package will still be maintained in, for example, 5 years? How is the history in terms of vulnerabilities and quality; is there an active community reporting weaknesses that are then addressed, or do reported issues linger in limbo for extended periods? Tools like libraries.io and Security Scorecard can be useful for finding out more.
Pinning Versions
One attack vector is when malicious actors take over popular packages and publish their own version with malicious content. If we have build or deploy processes that fetch the latest version of dependencies each time, they will automatically fetch the infected package. A measure here can be to pin the package versions we use, for example, in package-lock.json
or similar.
Use of SBOM
Software Bill Of Materials (SBOM) is an approach where we generate an overview of all dependencies with versions from our solutions. There are several more or less standardized file formats for this, which can also be archived or included in other solutions to simplify central monitoring.
6 - Documentation
All development projects must be documented. This is not only important for the continuity of the project but also for documenting security-related measures and any incidents.
Documentation is often seen as a necessary evil, and we are not always good enough at documenting our projects. Many believe that the source code is the best documentation, but it only says how an application works, not necessarily why it works the way it does, or what the actual intention is.
There are many reasons why we need to document the solution we are building; the most obvious is to ensure that new team members can understand what the project is about and also to ensure that information does not only live in the heads of individuals. Another important aspect of this is the documentation concerning the security of the project. If we do not document what measures are taken and why, or what threats we face and how we handle them, we place a huge responsibility on the team members who come after us: What seems like a logical and reasonable solution given a security context can seem backward and cumbersome to someone without the same context and competence, and we risk that important measures are removed to simplify the solution - at the expense of security.
Documentation Solution
The project must find a solution for where the documentation is stored that makes sense for the project. Remember that good documentation is as sensitive as the source code and must be treated accordingly. In many cases, it may be wise to use tools that support version control - in many cases, it may be wise to place the documentation together with the source code, or in separate repositories that can be used, for example, with Azure DevOps wiki.
What Should Be Documented
What we should document will vary from project to project. We should always have a design that provides insight into, for example, infrastructure, IAM, and data flow so that it is possible to review this at a later date.
The threat model must always be documented and maintained, and any mitigating measures must also be documented.
OWASP Top 10 has several points that can relate to the development environment and its setup, so if there are special configurations that need to be in place, this should also be documented to avoid weaknesses or vulnerabilities that arise if someone forgets important steps that are not written down.
One must also consider the criticality of the solution; if it is a system with 24/7 uptime requirements and is critical for the customer, the documentation must reflect this so that even the least experienced person on the team can troubleshoot problems without knowing the solution in detail.
7 - Internal Components
If the team relies on internal components such as servers, applications, or other assets managed by the team itself, there must be a routine for keeping these updated.
Many development teams do more than just write code. You might have virtual machines in the cloud or on-premise, with applications or services used in the delivery to the customer. You may manage integration solutions for the customer, containers, application servers, and much more.
Do you have a routine for keeping these updated, and are they part of the overall security efforts?
Important Reminders
Updates
If you manage servers, services, or other assets within the team, they must be kept up to date. You need to familiarize yourself with the release routines for new versions from the supplier’s side so that you can keep everything updated. It’s not always desirable to go for the latest version all the time, so it’s important to read the release notes or other documentation about the release to understand why it came out and what it solves.
Remember that the software we use should be supported to avoid errors and problems resulting from a component not supporting the latest version of another component.
Security Practices
All components we use must be part of the overall design, and we must have control over network openings, IAM, and how these are exposed both internally and externally. Without knowledge of this, it is difficult to say what threats we may face and thus also the mitigating measures.
Remember that the security of a solution should never depend on a single measure; therefore, try to isolate components as much as possible. By default, nothing should be allowed - whether it concerns access to resources or network in/out, but explicitly open for what should be permitted.
Logging, Monitoring, and Incident Handling
Remember that internal components must also be monitored. Even if it is off-the-shelf software, the same measures must be implemented here to ensure that we know what is happening and that the logs are trustworthy and cannot be manipulated, just like everything else we monitor.
8 - Security Testing
In line with other elements associated with team quality, we depend on testing to verify that we have achieved the goal. Security testing is an important part of this, as it allows us to demonstrate that the delivery is not vulnerable to certain attack methods.
Security testing should always be an element of all deliveries. Many associate security testing with penetration testing, but it is much more than that. Some forms of testing can be done automatically as part of CICD, while others are more manual and typically performed against a deployed solution.
Before starting security testing, it is important to understand what it can provide you - there is no single measure that solves all security problems, and no single test method that uncovers all weaknesses. Security testing is also one of the areas that often require specific expertise for the results to be good and/or interpreted correctly.
Warning
The use of tools like `nmap` and others used in connection with security testing must **always** be clarified with the owners of infrastructure and network, as it is difficult to distinguish between friendly testing and malicious attacks. This also applies internally at Bouvet; Intern-IT & Security must always be in the loop before you start a security test!
If this is not taken into account, it can have consequences, both for the customer relationship and for technical solutions against the network provider and Microsoft.
Test Environment
When conducting security testing against a running environment, it is important to always clarify this well in advance. Many types of testing can be destructive, so if the environments are not sufficiently separated, you risk affecting other environments than intended.
A good solution, especially if using infrastructure-as-code (IaC), is to have a pipeline that deploys a dedicated environment for security testing. If this is designed into the delivery from the start, it is often easy to set up environments identical to the production environment, where you can also copy databases and possibly run anonymization processes on the data.
Static Application Security Testing (SAST)
Static code analysis is a low-threshold technique that analyzes the code with dependencies to find weaknesses. SAST can be carried out completely automatically, and there are many good tools that can integrate this into CICD so that you can scan as part of the processes here.
SAST only checks the solution being created; it does not reveal anything about the configuration of the runtime environment, network, or other surrounding dependencies. Tools used to perform SAST are language-specific, so it is important to understand which tools provide the best results for the language and possibly framework used.
Dynamic Application Security Testing (DAST)
In contrast to SAST, DAST is a technique where a solution is tested in a running state. This is a language-agnostic test method, where, for example, a web application is tested by testing the frontend solution to find weaknesses. DAST can be automated, but often needs to be run manually for certain types of weaknesses to be tested.
DAST will only cover functionality exposed in the running solution, so if there is code that is part of the solution but not accessible to the DAST tool, it will not be tested either.