How To Guard Your Codebase Against Security Threats

In this article:
Subscribe to our blog:

Every developer will always have that gnawing doubt in the pit of their stomach once they hit “Commit changes.”

That doubt comes from the worry that they have overlooked an issue that could not just cause problems for users but could cause untold damage and costs to the company–a security flaw.

This concern is not unfounded, as even minor oversights in code can lead to significant vulnerabilities that malicious actors can exploit. Developers are heavily responsible for maintaining robust security measures. However, the complexity of modern software and the rapid pace of development can make it challenging to catch every potential security issue manually. This is where automated security tools, like Codacy, come in handy. 

Here, we want to show you how easy it is to use Codacy to highlight and fix security flaws in your code.

The Cost of Lax Security

According to IBM, the global average data breach cost in 2023 was $4.45 million, a 15% increase over three years. The global annual cost of cybercrime is forecasted to reach $10.5 trillion by 2025. In 2022, smaller organizations saw a significant increase in data breach costs, with companies between 500 and 5000 people experiencing at least a 20% jump in costs.

These numbers are all bad. Lax security costs. Attacks are going up, and costs associated with those attacks are going up. Discovering problems post-hoc will cost a company dearly, so the right approach is to emphasize security and prevention early in the software development lifecycle.

Integrating security measures early in the development process reduces the risk of costly breaches and saves time and resources in the long run. This “shift left” approach involves incorporating security practices and tools from the beginning of the software development lifecycle.

This sounds great in theory but rarely satisfies that gnawing doubt in practice. Why? Because security vulnerabilities are often a known unknown. An individual developer knows that their code might have security issues but not what these issues are. Thus, the most crucial step is integrating security testing into the development pipeline and providing developers with immediate feedback on potential security vulnerabilities in their code. This approach helps bridge the gap between theoretical knowledge of security best practices and practical application.

By implementing automated security testing tools directly into the development workflow, developers can:

  1. Identify vulnerabilities early: Catch potential security issues before they make it into production.

  2. Learn from mistakes in real-time: Understand specific security flaws in their code and how to fix them.

  3. Continuously improve: Build a more robust understanding of secure coding practices over time.

  4. Reduce the burden on security teams: Automatically catch common issues, allowing security experts to focus on more complex threats.

  5. Foster a security-first mindset: Make security an integral part of the development process rather than an afterthought.

Integrating tools like static application security testing (SAST), dynamic application security testing (DAST), and software composition analysis (SCA) into CI/CD pipelines can provide this crucial feedback loop. When combined with regular security training and clear guidelines, this approach can significantly mitigate that gnawing doubt and help developers build more secure applications with confidence.

Guarding Against Security Threats

OK, let’s have some incredibly dangerous fun. Here’s a simple Python Flask app overflowing with OWASP vulns (Let’s hope some AI scraper doesn’t take this as helpful code!).

from flask import Flask, request, jsonify, render_template_string, session
import sqlite3
import hashlib
import os
import pickle

app = Flask(__name__)
app.secret_key = 'supersecretkey'  # Used for session management

# Database setup
def init_db():
    conn = sqlite3.connect('vulnerable_app.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)''')
    c.execute('''INSERT OR IGNORE INTO users (username, password) VALUES ('admin', 'admin')''')
    conn.commit()
    conn.close()

# Injection Vulnerability
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        conn = sqlite3.connect('vulnerable_app.db')
        c = conn.cursor()
        # Vulnerable to SQL Injection
        c.execute(f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'")
        user = c.fetchone()
        conn.close()
        if user:
            session['username'] = username
            return 'Logged in as ' + username
        else:
            return 'Invalid credentials'
    return '''
        <form method="post">
            Username: <input type="text" name="username"><br>
            Password: <input type="password" name="password"><br>
            <input type="submit" value="Login">
        </form>
    '''

# Broken Authentication
@app.route('/admin')
def admin():
    if session.get('username') == 'admin':
        return 'Welcome to the admin page'
    else:
        return 'Access denied'

# Sensitive Data Exposure
@app.route('/hash_password', methods=['POST'])
def hash_password():
    password = request.form['password']
    # Weak hash algorithm (MD5)
    hashed_password = hashlib.md5(password.encode()).hexdigest()
    return jsonify({'hashed_password': hashed_password})

# XML External Entities (XXE)
@app.route('/parse_xml', methods=['POST'])
def parse_xml():
    xml_data = request.data
    try:
        # Vulnerable to XXE
        from lxml import etree
        doc = etree.fromstring(xml_data)
        return etree.tostring(doc)
    except Exception as e:
        return str(e)

# Broken Access Control
@app.route('/user/<int:user_id>')
def get_user(user_id):
    conn = sqlite3.connect('vulnerable_app.db')
    c = conn.cursor()
    # Direct object reference without proper authorization check
    c.execute("SELECT * FROM users WHERE id=?", (user_id,))
    user = c.fetchone()
    conn.close()
    if user:
        return jsonify({'id': user[0], 'username': user[1]})
    return 'User not found'

# Security Misconfiguration
@app.route('/debug')
def debug():
    # Exposing debug information
    return str(app.config)

# Cross-Site Scripting (XSS)
@app.route('/greet', methods=['GET', 'POST'])
def greet():
    if request.method == 'POST':
        name = request.form['name']
        # Vulnerable to XSS
        return render_template_string('<h1>Hello, {}!</h1>'.format(name))
    return '''
        <form method="post">
            Name: <input type="text" name="name"><br>
            <input type="submit" value="Greet">
        </form>
    '''

# Insecure Deserialization
@app.route('/load_object', methods=['POST'])
def load_object():
    serialized_object = request.data
    # Vulnerable to insecure deserialization
    obj = pickle.loads(serialized_object)
    return str(obj)

# Insufficient Logging & Monitoring
@app.route('/transfer_money', methods=['POST'])
def transfer_money():
    sender = request.form['sender']
    recipient = request.form['recipient']
    amount = request.form['amount']
    # Insufficient logging
    return f'{amount} transferred from {sender} to {recipient}'

if __name__ == '__main__':
    init_db()
    app.run(debug=True)

If you don’t already know, here’s a quick breakdown of what will make any security engineer cry in the above:

  1. Injection: The login function directly includes user inputs into the SQL query, making it vulnerable to SQL injection attacks.

  2. Broken Authentication: The admin function does not adequately protect admin functionality.

  3. Sensitive Data Exposure: The hash_password function uses MD5, a weak hashing algorithm vulnerable to attacks.

  4. XML External Entities (XXE): The parse_xml function parses XML data without disabling external entity references.

  5. Broken Access Control: The get_user function does not verify that the requester is authorized to access the specified user’s data.

  6. Security Misconfiguration: The debug function exposes application configuration, which should not be shown in production.

  7. Cross-Site Scripting (XSS): The greet function directly includes user input in the HTML response, making it vulnerable to XSS.

  8. Insecure Deserialization: The load_object function deserializes potentially malicious data.

  9. Using Components with Known Vulnerabilities: The same function uses pickle, which may have known vulnerabilities.

  10. Insufficient Logging & Monitoring: The transfer_money function performs sensitive actions without sufficient logging.

If we were relying on a human reviewer to catch all these, we might expect them to catch a few, maybe most, but doubtfully all. And we’ve all added a sneakily lgtm to keep moving code along the pipeline.

Instead, we can use Codacy Security to quickly and efficiently point out our issues. If we add this repo to Codacy, after a minute or so, this is what we get:

Eek. The list is so long that we can’t even screenshot it properly. Here are a few choice cuts:

  • User-controlled data from a request is passed to 'execute()'. This could lead to a SQL injection, and therefore, protected information could be leaked.

  • The application was found using `pickle`, which is vulnerable to deserialization attacks.

  • Detected user input used to manually construct a SQL string.

  • Possible hard-coded password: 'supersecretkey'

We can quickly see the problems, why they are problems, and the solutions. For instance, we have the issue: “The application was found using an insecure or risky digest or signature algorithm.”

Codacy tells us more about the problem as well as a solution:

We can use this information to implement a fix and quickly push again within a few minutes. The sheer number of problems in this codebase means we’ll be fixing them for hours, but in a regular codebase, when one or two security issues are highlighted, you can still get the code into production in moments. 

We can also use this data for other means.

Firstly, We can also use this output to track security patterns across our organization. Organizations can identify common vulnerabilities and trends by analyzing the security issues detected across multiple projects. This data can be used to:

  • Develop targeted training programs to address recurring security weaknesses.

  • Create or update coding standards and best practices specific to the organization's needs.

  • Prioritize security improvements based on the frequency and severity of detected issues.

  • Measure the effectiveness of security initiatives over time by tracking the reduction in specific vulnerability types.

  • Allocate resources more efficiently by focusing on the most prevalent or critical security concerns.

Secondly, it helps prevent new breaches the team might not know about or fully grok. Automated security scanning tools like Codacy Security can detect vulnerabilities that might be overlooked by developers or security teams, especially:

  • Newly discovered vulnerabilities in third-party libraries or frameworks

  • Complex security issues that require specialized knowledge to identify

  • Subtle flaws that may not be apparent during manual code reviews

  • Security risks introduced by new team members who may not be fully aware of the organization's security practices

  • Potential attack vectors that emerge as the application grows and becomes more complex

  • Issues arising from the interaction between different parts of the codebase may not be apparent when looking at individual components

By catching these issues early and automatically, the tool helps maintain a robust security posture even as the codebase evolves and new threats emerge.

Thirdly, we can use Codacy Security to maintain compliance with the security aspects of regulations. Automated security scanning tools can play a crucial role in ensuring and demonstrating regulatory compliance:

  • Continuously monitor code for compliance with specific security standards (e.g., OWASP Top 10, GDPR, HIPAA, PCI DSS).

  • Generate detailed reports that can be used as evidence during security audits.

  • Quickly identify and remediate non-compliant code before it reaches production environments.

  • Establish a documented, repeatable process for security checks, which is often required by regulations.

  • Track security metrics over time, demonstrating ongoing commitment to maintaining secure practices.

  • Automate parts of the compliance process, reducing manual effort and the potential for human error.

  • Adapt quickly to new or updated regulations by configuring the tool to check for specific compliance requirements.

This is how Logex has used Codacy to comply with ISO 27001. They even share their Codacy dashboards with auditors as they contain all the data the regulators need for certification. As Tim Von Loosbroek, LOGEX's head of infrastructure and security, says:

"By using the [Codacy] tool we can fulfill that requirement and prove to the auditor, and it's a big help for our developers."

Remember, the goal isn't to eliminate all doubt–some level of caution is healthy in software development. Instead, the aim is to provide developers with the tools, knowledge, and processes they need to address security concerns proactively and effectively throughout the development lifecycle.

By integrating Codacy Security into the development workflow, organizations can create a proactive approach to security, compliance, and high-quality code, reducing the incredible risk of penalties and reputational damage associated with insecure code.

RELATED
BLOG POSTS

How to Ensure Security Compliance in Modern Software Development
Non-compliance is a concept that should send a chill down the spine of any product leader. It can lead to genuinely company-ending ramifications. In...
OWASP Explained: Secure Coding Best Practices
From global finance to daily communication, software underpins every aspect of modern life. That's why a single data breach can shatter user trust,...
11 Common Java Vulnerabilities and How to Avoid Them
Java is a cornerstone of modern software development, powering everything from enterprise-level applications to Android apps and large-scale data...

Automate code
reviews on your commits and pull request

Group 13