Security is not something we can ever take for granted and no matter how secure a company’s infrastructure and firewalls are, a security issue in the code can lead to significant issues and potential breaches within any company.
So, the idea of needing code to be secure is an easy one to argue for. Yet – surprisingly – it’s still something that people may take lightly, focusing too much on specialists and scans to do the work for them. Rather than putting in a little extra effort to ensure code and applications are resistant to security vulnerabilities, which can be exploited by malicious actors.
This doesn’t just apply to software or data engineers, but all disciplines of engineering where our code sits need to take responsibility for the code they write and any vulnerabilities or issues that could be caused by the code they write or the modules they may be importing into their code.
Here are some key reasons why secure coding is important:
Protection of Sensitive Data
Many applications handle sensitive information such as personal data, financial records, or business-critical information. Secure coding practices help protect this data from unauthorized access, leaks, or theft by minimizing vulnerabilities like SQL injection or cross-site scripting (XSS).
Prevention of Exploits
Security flaws in code can be exploited to gain unauthorized access to systems, alter data, or disrupt services. Secure coding helps developers identify and mitigate potential weaknesses like buffer overflows, improper input validation, or broken authentication mechanisms, preventing attacks like malware injection and denial-of-service (DoS) attacks.
Regulatory Compliance
Globally, we are governed by strict data protection regulations such as POPIA, GDPR, HIPAA, and PCI-DSS. Secure coding is critical for ensuring that software complies with these regulations, avoiding legal penalties, and ensuring the privacy of users' data.
Reduction of Long-Term Costs
Fixing security vulnerabilities after a product has been released is far more expensive and time-consuming than addressing them during the development phase. By integrating secure coding practices early, organizations can save costs associated with post-release patches, security breaches, and potential fines.
Improved User Trust
Users are more likely to trust applications that are secure, particularly in sectors like finance or healthcare where data security is paramount. Breaches due to insecure code can lead to loss of customer trust, damaging a company's reputation and revenue.
Business Continuity
Security vulnerabilities can lead to system downtime, data loss, or reputational damage, which can disrupt business operations. By implementing secure coding practices, companies can ensure the resilience of their systems, reduce the risk of breaches, and maintain business continuity.
Security by Design
By embedding security into the software development life cycle (SDLC), secure coding promotes a "security-first" mindset. This prevents developers from relying solely on later-stage defenses like firewalls and intrusion detection systems and ensures that software is fundamentally designed with security in mind.
Mitigation of Zero-Day Vulnerabilities
Secure coding can help reduce the window of opportunity for attackers to exploit unknown or unpatched vulnerabilities, known as zero-day vulnerabilities. Even if a vulnerability is discovered, secure coding principles like defensive programming can reduce its impact.
Secure Coding Best Practices
Improving secure coding skills involves adopting best practices, using tools to identify vulnerabilities, and staying up to date on the latest security threats.
Here are some practical tips to help you enhance secure coding practices:
Validate Input
Why: Unvalidated input can lead to vulnerabilities like SQL injection, cross-site scripting (XSS), and buffer overflows.
Tip: Always validate and sanitize all user inputs. Ensure inputs conform to expected formats (e.g., numbers, emails, or dates). Use built-in libraries for input validation when possible.
Use Parameterized Queries
Why: This prevents SQL injection attacks, where attackers can manipulate SQL queries through input fields.
Tip: Use parameterized queries or prepared statements instead of dynamic SQL queries that concatenate user input.
Implement Strong Authentication and Authorization
Why: Weak authentication can lead to unauthorized access and privilege escalation.
Tip: Use strong password policies, multi-factor authentication (MFA), and token-based authentication. For authorization, apply the principle of least privilege (grant users only the access they need).
Encrypt Sensitive Data
Why: Encrypting sensitive data protects it from being exposed even if an attacker gains access to the database or files.
Tip: Use encryption for data at rest (e.g., using AES) and data in transit (e.g., using SSL/TLS). Be sure to securely manage encryption keys.
Avoid Hardcoding Secrets
Why: Hardcoded credentials or API keys can easily be exposed if the source code is compromised.
Tip: Store secrets in secure vaults or environment variables, never directly in the code. Use tools like AWS Secrets Manager or HashiCorp Vault.
Follow Secure Coding Standards
Why: Standards like OWASP Top 10 or the CERT Secure Coding standards provide a roadmap to avoid common security mistakes.
Tip: Integrate secure coding standards into your development process and continuously reference them during code reviews and design.
Conduct Regular Code Reviews
Why: Code reviews help catch potential security issues that might not be apparent to a single developer.
Tip: Make security a focal point during peer reviews. Using AzureDevOps, all code needs to be reviewed by engineers who will share the responsibility of ensuring your code is secure. Ensure reviewers focus on security as well as functionality.
Use Static Application Security Testing (SAST) Tools
Why: Automated tools can analyze your code and identify vulnerabilities early in the development process.
Tip: Integrate SAST tools such as SonarQube, Checkmarx, or Veracode into your continuous integration (CI) pipeline. These tools can help identify common vulnerabilities like buffer overflows, SQL injection, and cross-site scripting (XSS).
Employ Secure Libraries and Frameworks
Why: Many common tasks such as authentication, encryption, and input validation can be safely handled by well-tested libraries.
Tip: Use trusted libraries and frameworks that have a strong security track record. Regularly update them to patch known vulnerabilities (e.g., using OWASP dependency-check to scan for vulnerable dependencies).
Implement Error Handling and Logging
Why: Improper error handling can expose sensitive system information, and poor logging practices can make it harder to detect breaches.
Tip: Ensure errors are handled gracefully without revealing too much information to users. Log relevant security events but avoid logging sensitive information like passwords. Use log management tools to monitor security events and enable alerting for suspicious activity.
Use Content Security Policy (CSP) for Web Apps
Why: CSP helps protect against cross-site scripting (XSS) attacks by controlling the sources from which a browser can load content.
Tip: Define a robust Content Security Policy for your web applications to restrict where scripts, images, and other resources can be loaded.
Regularly Patch and Update Dependencies
Why: Outdated software components often contain security vulnerabilities that attackers can exploit.
Tip: Regularly update all third-party libraries, frameworks, and dependencies used in your applications. Use package managers and tools that help identify outdated or insecure packages.
Use Secure Session Management
Why: Improper session management can lead to session hijacking and unauthorized access.
Tip: Implement secure session handling with session expiration, use secure cookies, and ensure that sessions are invalidated on logout. Mark session cookies as HttpOnly and Secure to prevent access through JavaScript.
Learn from Security Incidents
Why: Post-mortem analysis of security incidents can provide valuable lessons and help prevent future vulnerabilities.
Tip: After every security breach or issue, conduct a thorough analysis and adjust your coding practices and security protocols accordingly.
Stay Up to Date on Emerging Threats
Why: The security landscape evolves constantly, and new vulnerabilities are discovered regularly.
Tip: Follow resources like OWASP, CERT, or security blogs, and subscribe to mailing lists for security updates related to the tools and frameworks you use. Attend security conferences or take security-focused courses.
Some practical code examples
I thought it would only be fair though to provide you with some basic examples of what secure code looks like against unsecure code. As you can see from the below examples – it’s not always immediately clear that code isn’t secure because the code solves the immediate functional solution so effortlessly. These are just basic examples – with most applications containing far more complicated code, the chance of error only increases – which is what makes embedding these skills into your regular coding practices so important.
These examples, which use a mix of Python and JavaScript code, highlight common vulnerabilities and the best way to mitigate them.
SQL Injection
Insecure Code:
Python
# Insecure: Directly concatenating user input into a SQL query
user_id = request.GET.get('user_id')
query = "SELECT * FROM users WHERE id = '" + user_id + "'"
cursor.execute(query)
Problem: The code is vulnerable to SQL injection. If a user inputs something like 1'; DROP TABLE users; --, the query will drop the users table.
Secure Code:
Python
# Secure: Using parameterized queries to avoid SQL injection
user_id = request.GET.get('user_id')
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
Solution: Parameterized queries prevent user input from being treated as part of the SQL command, eliminating the risk of injection.
Cross-Site Scripting (XSS)
Insecure Code:
JavaScript
// Insecure: Directly rendering user input in the browser
let userComment = getUserComment(); // User input from a form
document.getElementById('comment').innerHTML = userComment;
Problem: If a malicious user enters a script like <script>alert('Hacked!');</script>, it will execute in the browser, leading to XSS attacks.
Secure Code:
JavaScript
// Secure: Escaping user input before rendering it in the browser
let userComment = getUserComment();
document.getElementById('comment').innerText = userComment; // Escape user input
Solution: Using innerText or a dedicated library for escaping output, such as DOMPurify, ensures that user input is treated as text, not executable code.
Hardcoded Secrets
Insecure Code:
Python
# Insecure: Hardcoding sensitive credentials in the code
API_KEY = "12345-abcde-67890-fghij"
Problem: If the code is shared or compromised, the sensitive key is exposed, potentially giving attackers access to APIs or systems.
Secure Code:
python
# Secure: Storing secrets in environment variables
import os
API_KEY = os.getenv('API_KEY')
Solution: Store sensitive data like API keys in environment variables, which can be configured securely in deployment environments without hardcoding them into the code.
Weak Password Storage
Insecure Code:
Copy code
# Insecure: Storing plain text passwords
user_password = request.POST['password']
store_password_in_db(user_password)
Problem: Storing plain text passwords makes them vulnerable if the database is compromised.
Secure Code:
Python
# Secure: Hashing passwords before storing them
import bcrypt
user_password = request.POST['password']
hashed_password = bcrypt.hashpw(user_password.encode('utf-8'), bcrypt.gensalt())
store_password_in_db(hashed_password)
Solution: Use a secure hashing algorithm like bcrypt, scrypt, or Argon2 to hash passwords before storing them. Hashing ensures that even if the database is compromised, the passwords are not directly exposed.
Insecure File Uploads
Insecure Code:
Python
# Insecure: Saving file uploads without validation
file = request.FILES['file']
file.save(os.path.join("/uploads", file.name))
Problem: Attackers could upload malicious files (e.g., scripts, executables) that get executed on the server.
Secure Code:
Python
# Secure: Validating and sanitizing file uploads
import os
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def is_allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
file = request.FILES['file']
if is_allowed_file(file.filename):
safe_filename = secure_filename(file.filename)
file.save(os.path.join("/uploads", safe_filename))
Solution: Validate the file type and sanitize the file name before saving it. Use a function like secure_filename from the Flask library or a similar utility to prevent directory traversal attacks.
Improper Error Handling
Insecure Code:
Python
# Insecure: Leaking sensitive error information
try:
# Code that might raise an exception
except Exception as e:
return f"Error: {str(e)}"
Problem: Exposing raw error messages can reveal internal system details, making it easier for attackers to find vulnerabilities.
Secure Code:
Python
# Secure: Logging the error while providing a generic message to the user
import logging
try:
# Code that might raise an exception
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
return "An unexpected error occurred. Please try again later."
Solution: Log detailed errors to a secure log system while displaying a generic message to the user, avoiding the disclosure of internal information.
Insecure Session Management
Insecure Code:
Python
# Insecure: Not using secure flags for cookies
session['user_id'] = user_id
Problem: Cookies may be vulnerable to interception in transit if they are not marked as secure.
Secure Code:
Python
# Secure: Using secure and HttpOnly flags for cookies
session['user_id'] = user_id
app.config.update(
SESSION_COOKIE_SECURE=True, # Only send cookies over HTTPS
SESSION_COOKIE_HTTPONLY=True, # Prevent JavaScript access to cookies
)
Solution: Enable SESSION_COOKIE_SECURE and SESSION_COOKIE_HTTPONLY flags to prevent cookies from being transmitted over unsecured channels and accessed via JavaScript.
Conclusion
Secure coding is critical and it’s important that everyone who touches code becomes familiar with the skills required to make code secure. However, coding is more than just about the functional code you write, but also the data as well, which is why in my next blog post – I will be looking at tips to securing your data in your code as well.