Web Application Security: The Ultimate Guide to Coding Best Practices
Explore best practices and core considerations for writing secure code across web applications in this all-encompassing guide.
Join the DZone community and get the full member experience.
Join For FreeWeb applications have become deeply integrated into business operations and everyday life. However, this reliance also introduces major security risks if applications are not properly coded and configured. Implementing secure coding practices is, therefore, essential for any web application. Not only does this protect sensitive user data, but it also safeguards against legal liabilities and reputation damage if a breach does occur.
This article outlines key principles and methods that will guide secure web application development, covering common threats, secure design considerations, proper input handling and output encoding, authentication and access controls, session management, and more. Additionally, it provides language-specific guidance for Java, .NET, PHP, Python, and JavaScript environments.
Following these coding standards and procedures will lead to more hardened, resilient web apps that can withstand the growing sophistication of cybercriminals. Even if you utilize web frameworks that handle security components automatically, understanding these core concepts is vital for evaluating potential weaknesses.
Common Web Application Threats
The diversity of technologies and complexity within modern web apps provides attackers many potential routes to infiltrate systems and data. While an exhaustive list is outside this article’s scope, engineers and architects should be aware of the following high-level threats:
Broken Authentication
Implementing authentication incorrectly or weakly can enable attackers to compromise user passwords, keys, session tokens, and gain unauthorized access.
Injection Attacks
Injection introduces malicious code or parameters into web inputs. Examples include SQL injection, command injection, and more. This can lead to data loss, corruption, unauthorized access, and more. Over 65% of web app attacks involve injection at some level.
Cross-Site Scripting (XSS)
Cross-site scripting (XSS) attacks inject client-side scripts, often JavaScript, into vulnerable pages viewed by other users. This can bypass access controls, launch actions, and access data using the victim’s credentials.
Cross-Site Request Forgery (CSRF)
Cross-site request forgery (CSRF) is a form of web application attack where the transmit malicious requests from an already authenticated user to perform state-changing actions without consent. Therefore, the web server needs a mechanism to determine whether a legitimate user generated a request via the user’s browser to avoid such attacks.
Sensitive Data Exposure
Many web apps require access to sensitive information like financial data and personal healthcare records. Improper disclosure or lack of encryption can expose this data.
Security Misconfiguration
Insecure default configurations, unused features, unnecessary services, and/or misconfigured HTTP headers can provide avenues for compromise. Work diligently to secure any configurations and avoid exposure.
Secure Design Principles and Methodologies
While individual coding practices are vital, consistently implementing proper security also requires organizational integration throughout the software development lifecycle (SDLC). This section outlines overall models developers should follow, then later sections will detail specific methods.
Practice Defense in Depth
Think about web application security like an onion: Defense in depth layers multiple, diverse controls and safeguards to protect assets and data. If one mechanism fails, another layer remains unaffected and protects the application.
Adopt a Zero-Trust Approach
Zero-trust models mandate strict identity verification and least privilege access inside network perimeters. This limits damage from compromised user accounts.
Prioritize Simplicity
Simple designs with minimal components are easier to secure and test versus unnecessarily intricate architectures. Always favor the simplest approach that still meets business needs.
Default to Secure Settings
Choose the most secure configurations, protocols, libraries, frameworks, and controls as defaults rather than intentionally relaxing policies later.
Principle of Least Privilege
The principle of least privilege is a security practice that requires users and identities to be granted the minimal required access to perform their intended tasks — and no more.
In practice, admins only allocate the minimal necessary access, authorizations, and capabilities based on each user or process’ role.
Fail Securely
Failure is inevitable — whether via hardware failure, network outages, flawed code, and or any multitude of system failures. Systems should fail without introducing new vulnerabilities by maintaining backup states with appropriate access controls enabled.
Separation of Duties
When managing stages of the SDLC, it is essential to segment development, testing, and production environments. Separate roles are needed for code deployment vs. auditing application logs; different accounts for elevated privileges like sys admin vs. regular users are also critical.
Avoid Security by Obscurity
Never assume that secrecy around design mechanisms provides security. Security should work even if some implementation details become publicly known.
Secure Input Handling
One leading cause of web app vulnerabilities is improperly handling, restricting, sanitizing, and validating data inputs. Attackers craft malicious payloads by inserting characters and queries that break out of the original data context and interfere with the structure of underlying interpreters like SQL, XML, HTML, JavaScript, and more.
For example, without escaping special characters, this search input could terminate an original SQL query and append new database commands:
User search input: ‘ OR 1=1 --
Resulting SQL query:
SELECT * FROM Users WHERE Name = ‘‘ OR 1=1 --' AND Age > 30
This manipulates the query to return all users, ignoring the original filtering. The following sections outline key methods to help prevent such injection attacks.
Validate All Inputs
The first line of defense is validating that all supplied inputs match the expected general format, length, data type, and content before additional parsing. For example, it is critical that you enforce maximum characters for names and validate email address formats. This avoids passing malformed data downstream that could exploit vulnerabilities later on.
Limit Input Sizes
Establish and enforce maximum length values for all input strings and parameter values. This will help block overly large payloads.
Whitelist Allowed Characters
When possible, configure filters or regex patterns to explicitly define any permitted special characters for each input parameter rather than trying to blacklist every bad variant.
As an example, only permit alphanumeric characters for usernames or only allow decimal digits for currency values.
Encode or Escape Outputs
Many injection attacks can be prevented by encoding or escaping data outputs that will flow into sinks like HTML pages, system commands, or SQL queries. The examples below outlines common encoding methods by language or environment.
.NET
Use native encoding and escaping methods like System.Web.HttpUtility.HtmlEncode
or System.Web.HttpUtility.UrlEncode
:
string encodedString = Server.HtmlEncode(inputString);
Java
Import and utilize the org.apache.commons.lang.StringEscapeUtils
methods:
HtmlEscape.escapeHtml(inputString);
PHP
PHP has many native escape functions like htmlentities
and htmlspecialchars
:
$encodedString = htmlentities($inputString);
Python
Import standard cgi
and uuid
libraries with escape functions:
from cgi import escape
inputString = escape(inputString)
JavaScript
Escape inputs via methods like encodeURI
and encodeURIComponent
:
let escapedInput = encodeURIComponent(inputString);
Secure Authentication Practices
Authentication weaknesses frequently lead to unauthorized access and severe data breaches. All communication channels should be encrypted via TLS, and several practices outlined below help mitigate common account or session vulnerabilities.
Hash and Salt Passwords
Apply secure hashing algorithms like Bcrypt
, Scrypt
, Argon2
, or PBKDF2
when storing account passwords. Additionally, utilize salting with random bytes to protect against rainbow table attacks.
Temporary Lockouts
Lockout accounts after a certain threshold of consecutive failed login attempts to slow brute force password attacks. Allow genuine users to unlock via email confirmation or after a time delay.
Session Management
- Only transmit session IDs over encrypted transport channels
- Generate random session IDs of at least 128 bits in length
- Regenerate session IDs after authentication state changes like user logins or privilege escalations
Account Access Monitoring
Actively track login locations, timestamps, failed attempts and other activity to identify suspicious credential usage.
Multi-Factor Authentication
Expand beyond singular authentication factors like lone passwords. Options include one-time codes via SMS, email, applications, USB keys, biometrics, push notifications, and more.
Principle of Least Privilege
As defined above, this practice restricts access to the minimal needs per individual. Follow the separation of duty principles — limit account capabilities only to essential access levels needed for each user or process.
Secure Session Management
Ineffective session tracking frequently enables attackers to hijack active user sessions or conduct fixation attacks by forcing users into pre-set sessions.
Rotate Session Identifiers
Issue a new randomized session ID at user login, then periodically regenerate IDs within the active session to prevent fixation.
Expire Sessions
Sessions should expire client-side after a period of inactivity, requiring re-authentication. Server-side absolute timeouts additionally protect if client-side cookies are purged.
Regenerate IDs on Privilege Changes
Elevating user privileges, such as ordinary to admin status, should spawn a new session ID to protect the elevated session from prior lower-privilege fixes.
Use Server-Side Session Stores
Session data should be server-generated and stored server-side only, encrypted if sensitive. Client-side session data in cookies allows manipulation or disclosure threats.
Destroy Sessions on Logout
Clear session data on the server when users log out. Server-side languages often provide session destroy/regeneration functions.
Secure Data and Transport Encryption
Various web technologies and languages provide built-in methods for applying encryption protections.
Transport Layer Security (TLS)
Transport Later Security (TLS) is a security protocol that allows secure communication and messaging over a network. Commonly used in communication channels, it also is predominantly used in securing HTTPS.
Encrypt all transport channels and connectivity via TLS 1.2+ and disable legacy protocols. This includes application accessed internally or externally via domains, IP addresses, ports and more.
Utilize current best practice cipher suites and key lengths, specifically:
Data-at-Rest Encryption
If storing sensitive data like credentials, personal details, or healthcare records, it is important to implement filesystem or database encryption modules to protect inactive data at rest.
Parameter Encryption
Irreversibly encrypt parameters like account IDs in cookies/URLs which have higher base64 encoding risks versus typical transport encryption.
Field Encryption
When full database encryption is not possible, use field encryption APIs in frameworks like Hibernate or ADO.Net to encrypt specific high-risk columns with sensitive personal details.
Cross-Origin Resource Sharing (CORS)
CORS defines how web apps portioned into different domains can still request resources securely across domain boundaries. Enable CORS selectively only for domains requiring API access.
Validate that incoming HTTP requests originate from allowed domains by checking:
- Origin request header against white list origins
- Preflight OPTIONS request access-control headers
Additional Controls by Language or Framework
This section outlines additional language-specific injection prevention and security methods available in common web development environments.
Java and J2EE
- Utilize parameterized OS commands
- OWASP ESAPI encoding functions
- Java Servlet filter input validation
- Enable Web Application Firewall (WAF) features in servlet containers
.NET
- Input validation with
System.ComponentModel.DataAnnotations
- ASP.NET request validation feature
- Encoding with
HttpUtility ant
XSS functions - WAF modules integrated with IIS
PHP
- Input filter functions like
filter_var()
andfilter_input()
- Prepared
mysqli
statements with parameterized input values - Use PHP Data Objects (PDO) with prepared statements
Python
- Flask-WTF form input validation
- Server-Side Template Injection prevention by escaping Jinja2 templates
- Use WSGI middleware validation wrappers
Node.js and JavaScript
- Validation via Node.js validator modules
- Escape server-side JS outputs in EJS templates
- Enable Express.js router middleware validation filters
Secure Development Practices
While designing and programming methods are at the crux of application security, broader organizational secure coding policies and processes build essential support structures. Let's dive into the specifics for some of these secure development practices:
Security Training
Provide both general secure coding education as well as application-specific training — particularly when adding new developers on a project.
Specific security trainings can include:
- General security awareness training
- Compliance training
- Cloud security training
- Social engineering
Peer Code Reviews
Require peer code reviews to detect logical vulnerabilities before pushing to QA or production environments. Enable approval workflows in source version control systems.
Static Application Security Testing (SAST)
Static application security testing (SAST) scans application source code to detect vulnerabilities early on alongside interactive programming using IDE plugins or CI/CD integration.
Dynamic Analysis Security Testing (DAST)
Dynamic analysis security testing (DAST) detects vulnerabilities by actively testing running applications, thus simulating attacks against production systems. To combat vulnerabilities, be sure to schedule periodic scans.
Web Application Firewall (WAF)
A web application firewall (WAF) filters incoming HTTP requests to block SQL injections, cross-site scripting, protocol violations, and other abnormalities.
Separate Development, Testing, and Production
Isolate development, testing and production environments with separate code bases, configurations, and credentials to contain issues emerging during coding or evaluation.
Patching and Upgrades
Consider security an iterative, ongoing process throughout the application lifecycle rather than a one-time fix. Evaluate and implement relevant patches, upgrades, and security fixes often in all layers of the technology stack, including:
- Application code
- APIs or web services
- Frameworks and libraries
- Operating systems
- Network devices
- Runtime environments (Java, .NET Core)
Deprecate Unsupported Platforms
Actively monitor end-of-life and end-of-support statuses for software and hardware components. From there, migrate away from outdated tools or platforms that are losing vendor security updates.
Continuous Threat Modeling
Threat modeling is the practice of reviewing the architecture and/or design of a system and its environment with the goal of identifying, prioritizing, and addressing potential security and privacy risks before they are exploited. To strengthen the security of code within web applications, revisit threat models and risk assessments periodically to identify new attack vectors necessitating added protections.
Refresher Training
Provide secure development refresher education regularly — not just at project kickoffs. Update teams on new attack methods, tools, protective controls, and more.
Retiring Applications
When preparing for retirement, be sure to:
- Securely archive sensitive data not transferring to new systems
- Disable all application entry points and connectivity
- Shut down unused servers, databases, APIs, and other accounts
Conclusion
This guide provides a broad overview of proactive secure coding principles for designing, developing, and deploying robust web applications resistant to attacks. Adopting these measures certainly demands additional effort. However, the increasing frequency and sophistication of hacking demands such diligence to protect sensitive user data and business-critical systems.
Organizations must move beyond viewing security as an inconvenience or barrier to velocity. Instead, integrate it seamlessly throughout project lifecycles via training, standards, automation tooling, and testing. While no application can provide absolute protection against threats, consistently implementing processes outlined here will substantially improve resilience and trustworthiness.
Opinions expressed by DZone contributors are their own.
Comments