How to develop a mental model for thinking about security in Web Apps
A picture of the North Korean Supreme Leader and his security detail
Security may not earn you or your company more money, but security will definitely save you money.
Security is an overarching concern in computing. If we took the TCP/IP protocol suite as a reference, we could discuss security on every layer and still not cover every detail. However, the focus of this article is on security in the application layer. Web application security is about the security of websites, APIs, and web services in general. Security should be at the forefront of your plans when designing web applications. It should not be an afterthought, especially when we have users’ data that we must protect lest we run foul of compliance and regulation standards.
When web applications are not protected, users and companies’ data are put at risk. As a technical person, you’re in charge of ensuring that these things do not happen. Common examples of security attacks include cross-site scripting (XSS). XSS is a type of a broad class of attack vectors called injection attacks where an attacker injects malicious scripts into a website using their profile or any other means possible and then uses that website to send the script to different end users of the same website. These malicious scripts are attached to a trusted website, and they are executed on the browsers of other unsuspecting end-users thereby gaining access to cookies, session tokens, and other sensitive information retained by their browser. Far worse, the script can even rewrite the HTML content of the website. XSS attacks occur very often. Here is a recent vulnerability in the Facebook OAuth that was discovered by a researcher.
Another example of a security attack is smart contract hacks which occur very often in the blockchain ecosystem. Smart contracts are notorious for being very vulnerable to security breaches and when they aren’t audited before deployed to the blockchain, they have a high risk of being exploited and people lose a lot of money when these breaches occur. A very recent example is the Opyn smart contract hack where the hacker made away with about $371000. You can read about it here, or watch the very explanatory video below.
In order to protect our web apps from attacks, we need a mental model for thinking about security. The planning, principles, and patterns that are applied to designing the architecture of web applications are also transferable to the process of securing a web app. Having a mental model for thinking about application security helps to identify key areas to secure in our web apps, teaches us to be proactive and gives us direction when we need to deal with security breaches.
You have to start with a security-first mindset.
To develop a mental model for thinking about security in web applications, you have to start with a security-first mindset. Starting with a security-first mindset means making security considerations a priority throughout the process of building web applications.
When you don’t start with a security-first mindset, there are two ways you often think about security:
- Security through obscurity: This is done when you try to keep your application safe by minimizing its visibility. This is often how developers get away with running poorly secured vulnerable applications.
- Security through ignorance: Totally ignoring security. Almost every developer is/was a culprit here.
These two ways are obviously insufficient and can only go far before web apps built off of them are breached.
Starting with a security-first mindset means elevating security to the top of our priorities when designing web applications. And when we do this, we come up with an essential principle: Security by default or Security by design. This principle ensures that we don’t patch code when breaches occur, and we don’t totally ignore security. These principles essentially keep our web apps safe by ensuring that our design is safe from the ground up.
Security by Design or Security by Default Principle
Security by Design means adopting a security architecture for your application by applying a set of core security principles to building features, controls, and processes in your application in order to uphold the core pillars of information security. The core pillars of information security are:
- Confidentiality: Ensure that data is only accessible to authorized users.
- Integrity: Ensure that data is not tampered or altered by unauthorized users.
- Availability: Ensure that systems and data are available to authorized users only when they need it.
These three core pillars should serve as a guide when thinking about security in web applications. When starting the design of a new application or refactoring an existing application, you can consider these three core pillars by asking these questions:
- Is the process surrounding this feature as safe as possible? In other words, how is the confidentiality of this process being protected?
- If I were an attacker, how will I abuse this feature? In other words, how can the integrity of this feature be protected?
- Is this feature, control, or process required to be available by default? In other words, what is the scope of availability of this feature?
These questions are credited to Andrew Van Der Stock who called them ‘Thinking Evil’.
“To understand this field, you must understand the threats and attacks to defend against them. I am reasonably certain anyone can learn how to attack if they Think Evil for long enough. It’s far easier to Think Evil and destroy than it is to create solid software.” ~ Adrew Van Der Stock.
Apply the Security by Design principle by adopting a Security Architecture
Adopting a software architecture is not really about throwing a bunch of tools into our web application, but more about how you think through building safer controls, processes, and features in our application. By following these guides, you are on our way to adopting a very reliable software architecture for your applications.
Although it is important to consider the core pillars above when building applications, they are quite vague and broad. We can be more specific about what these core pillars actually mean by outlining a set of security principles that can help us nail them down.
Security Principles to consider when adopting a Security Architecture for your application.
- Avoid Security by Obscurity: The security of a system should not be reliant upon keeping details hidden or upon source code being kept secret. Rather, the security of a system should rely on factors including setting reasonable password policies, setting business transaction limits, having a solid security architecture, and setting up a fraud and audit controls. For example, keeping secrets and environment variables out of a repository is a good practice, and this is a common example of security by obscurity. A better security approach will be to set the right authorization for who can view, use, or modify the secrets when the application is in production.
caveat: Keeping secrets and environment variables out of a repository is still a recommended practice and should be strictly followed.
- Keep Security Simple (KSS): Complex architecture makes it a little difficult to detect security flaws in a system. When feasible, favor a simpler architecture or design over a complicated one.
- Separation of roles: This is a good way to control fraud in a system. When roles are clearly defined, the access level for each role provides visibility and an audit trail into what activities the role performs in a system. For example, an application monitoring platform like Sentry defines the role of an admin/owner of an account with responsibilities like adding a new user to an account, upgrading an account, etc. A non-admin user should not be able to upgrade or add a new user. That is the separation of roles.
- Apply the principle of least privilege: The principle of least privilege recommends that an account or user has just about enough authorization to perform their actions and nothing more. For example, if a logging service should only have access to read and write logs to a file, under no circumstances should the logging service have access to the database.
- Apply the principle of defense in depth: This principle states that layered security increases the security of a system as a whole. If an attack causes one security mechanism to fail, other security layers may still provide the necessary mechanisms to keep the systems safe. A very common example of this is multi-factor authentication where a user will need to present two or more pieces of evidence to an authentication system before being granted access to system resources.
- Establish secure defaults: Secure defaults speak to the principle of defense in depth in the sense that the layers of security should be enabled on a system by default and it should be up to the user to disable whichever they don’t want. For example, 2FA should be enabled by default for every user. However, a user may choose to disable it in their account settings. This may slow down user acquisition because it may interrupt a seamless signup process, but it will certainly make acquired users more secure.
- Minimize attack surface area: With every new feature comes new risks to an application. You should aim to reduce the overall risk in a web application. To minimize the risk that a new feature brings into an application, ask ‘Thinking evil’ questions and do a thorough security check before implementing the feature.
- Don’t trust third-party services: This speaks for itself. Use third-party tools and services that have a lot of eyes on them. All third-party tools should be checked for security vulnerabilities. Tools should be updated regularly and please pay attention to GitHub Security Advisories and take recommended actions. Automate the process of checking third-party tools by using tools like Snyk in your CI/CD pipeline.
- Fail Securely: When a process fails, credentials or data sent by a user should not be revealed in the error messages sent to a service or user to avoid security vulnerabilities like reflected XSS. Essentially, error messages should not be too revealing. For example, a failed authentication error message like ‘username or password is not correct’ is much better than ‘username is not correct’ or ‘password is not correct’, which are too revealing.
- Fix security issues correctly: Once a security issue is identified, the vulnerable components should be isolated, if possible, temporarily put on maintenance. The scale of the issue should be investigated; the codebase should be thoroughly scanned for adequate coverage before the issue is being fixed and tested thoroughly.
In conclusion, it’s not enough to claim ignorance about applying the best security practices when building applications anymore. Building a security-first mindset is even important now, more than ever before. As we design more applications, let’s endeavor to create architecture documents that contain security discussion in each and every feature, how the risks are going to be mitigated, avoided, transferred, or ignored, and what was actually done during coding.