ServerlessBase Blog
  • Introduction to Application Security

    A comprehensive overview of application security fundamentals and common vulnerabilities every developer should understand.

    Introduction to Application Security

    You've deployed your application, configured your database, and set up your reverse proxy. Everything looks good. Then you get an email: "We found a critical vulnerability in your application." Suddenly, your weekend plans are gone, and you're staring at a security report that looks like it was written in a foreign language.

    Application security isn't something you add after the fact. It's a fundamental part of how you build software. Every line of code you write is a potential entry point for attackers. Understanding application security means understanding how attackers think and how to close those entry points before they become problems.

    This guide covers the fundamentals of application security, common vulnerabilities you'll encounter, and practical steps to secure your applications.

    What is Application Security?

    Application security (AppSec) is the practice of protecting applications from threats that could result in the loss of information, revenue, or reputation. It encompasses the entire software development lifecycle, from design and development to deployment and maintenance.

    Think of application security as a series of defenses you build around your application. Each defense addresses a specific type of attack. When one defense fails, another should catch the attacker. This layered approach is often called "defense in depth."

    The OWASP Top 10

    The Open Web Application Security Project (OWASP) publishes a list of the most critical web application security risks. This list is updated periodically and serves as a baseline for understanding application security. The current Top 10 includes:

    RiskDescriptionImpact
    Broken Access ControlUsers can access resources they shouldn'tData exposure, privilege escalation
    Cryptographic FailuresSensitive data not protected or improperly protectedData breaches, identity theft
    InjectionUntrusted data sent to an interpreterData loss, system compromise
    Insecure DesignSecurity flaws in design phaseFundamental vulnerabilities
    Security MisconfigurationImproperly configured security controlsEasy exploitation, data exposure
    Vulnerable and Outdated ComponentsUsing libraries with known vulnerabilitiesRemote code execution
    Identification and Authentication FailuresWeak authentication mechanismsAccount takeover, fraud
    Software and Data Integrity FailuresTrusting unverified code or dataSupply chain attacks, data tampering
    Security Logging and Monitoring FailuresInadequate logging and monitoringDetection delays, slow response
    Server-Side Request Forgery (SSRF)Manipulating server to make requests to internal resourcesData exfiltration, internal network access

    These risks aren't theoretical. They're the most commonly exploited vulnerabilities in real-world applications. Understanding them is the first step toward securing your applications.

    Common Vulnerability Types

    1. Injection Vulnerabilities

    Injection vulnerabilities occur when untrusted user input is sent to an interpreter as part of a command or query. The most common types include SQL injection, command injection, and LDAP injection.

    SQL Injection Example:

    -- Vulnerable code
    const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
    const result = db.execute(query);
     
    -- Attacker input: ' OR '1'='1
    -- Result: Returns all users regardless of password

    Secure Approach:

    -- Parameterized query
    const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
    const result = db.execute(query, [username, password]);

    Parameterized queries separate code from data, preventing the interpreter from treating input as executable code.

    2. Cross-Site Scripting (XSS)

    XSS occurs when an application includes untrusted data in a web page without proper validation or escaping. This allows attackers to inject malicious scripts that execute in the browser of other users.

    Stored XSS Example:

    // Vulnerable: User input rendered directly
    app.get('/profile', (req, res) => {
      const bio = req.query.bio; // User-provided bio
      res.send(`<h1>${bio}</h1>`); // No escaping
    });
     
    // Attacker provides: <script>alert('XSS')</script>
    // Result: Script executes in all visitors' browsers

    Secure Approach:

    // Use a sanitization library
    const sanitizeHtml = require('sanitize-html');
     
    app.get('/profile', (req, res) => {
      const bio = sanitizeHtml(req.query.bio);
      res.send(`<h1>${bio}</h1>`);
    });

    3. Cross-Site Request Forgery (CSRF)

    CSRF tricks a user's browser into making an unintended request to a web application where the user is authenticated. The attacker doesn't steal credentials; they leverage the user's existing session.

    CSRF Attack Example:

    <!-- Attacker's website -->
    <img src="https://bank.com/transfer?to=attacker&amount=10000" />
     
    <!-- When user visits this page, their browser sends the transfer request -->
    <!-- because they're authenticated to bank.com -->

    Mitigation:

    // CSRF tokens
    app.use(csrfProtection);
     
    app.get('/transfer', (req, res) => {
      res.send(`
        <form action="/transfer" method="POST">
          <input type="hidden" name="_csrf" value="${req.csrfToken()}" />
          <input type="text" name="to" />
          <input type="number" name="amount" />
          <button type="submit">Transfer</button>
        </form>
      `);
    });

    Security Best Practices

    1. Input Validation

    Validate all input on both the client and server. Client validation provides a better user experience, but server validation is mandatory.

    // Validate email format
    function isValidEmail(email) {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      return emailRegex.test(email);
    }
     
    // Validate and sanitize
    function sanitizeInput(input) {
      return input.trim().replace(/[<>]/g, '');
    }

    2. Principle of Least Privilege

    Run applications and services with the minimum permissions required. A web server should never run as root or with database admin privileges.

    # Docker example - run as non-root user
    FROM node:18-alpine
    RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
    USER nodejs
    COPY --chown=nodejs:nodejs package*.json ./
    RUN npm ci --only=production
    COPY --chown=nodejs:nodejs . .
    USER nodejs
    EXPOSE 3000
    CMD ["node", "server.js"]

    3. Secure Authentication

    Use strong authentication mechanisms. Implement multi-factor authentication (MFA) for sensitive operations.

    // Hash passwords with bcrypt
    const bcrypt = require('bcrypt');
     
    async function hashPassword(password) {
      const salt = await bcrypt.genSalt(10);
      return bcrypt.hash(password, salt);
    }
     
    async function verifyPassword(password, hash) {
      return bcrypt.compare(password, hash);
    }

    4. Secure Communication

    Always use HTTPS. Enforce HTTPS and redirect HTTP traffic.

    # Nginx HTTPS configuration
    server {
        listen 80;
        server_name example.com;
        return 301 https://$server_name$request_uri;
    }
     
    server {
        listen 443 ssl http2;
        server_name example.com;
     
        ssl_certificate /etc/ssl/certs/example.com.crt;
        ssl_certificate_key /etc/ssl/private/example.com.key;
     
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;
    }

    5. Regular Security Updates

    Keep dependencies updated. Use tools to scan for vulnerabilities in your dependencies.

    # Scan for vulnerabilities
    npm audit
     
    # Update dependencies
    npm audit fix
     
    # Use a vulnerability scanner
    npm install -g snyk
    snyk test

    Security Testing

    Static Application Security Testing (SAST)

    SAST analyzes your code for security vulnerabilities before it's deployed. Tools like ESLint security plugins, SonarQube, and CodeQL can catch issues during development.

    // .eslintrc.js - enable security rules
    module.exports = {
      rules: {
        'no-eval': 'error',
        'no-implied-eval': 'error',
        'no-new-func': 'error',
        'no-script-url': 'error',
        'no-unsafe-innerhtml': 'error',
      }
    };

    Dynamic Application Security Testing (DAST)

    DAST analyzes your running application for vulnerabilities. Tools like OWASP ZAP, Burp Suite, and Nessus can identify issues that static analysis might miss.

    Dependency Scanning

    Regularly scan your dependencies for known vulnerabilities. Tools like Snyk, Trivy, and Dependabot can alert you to security issues in your dependencies.

    # Trivy scan for vulnerabilities
    trivy image myapp:latest
     
    # Snyk scan
    snyk test

    Security Headers

    Implement security headers to protect against common attacks.

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    Conclusion

    Application security is an ongoing process, not a one-time task. You can't secure your application once and forget about it. Threats evolve, vulnerabilities are discovered, and new attack techniques emerge.

    The key is to build security into your development process. Validate input, use parameterized queries, implement proper authentication, keep dependencies updated, and regularly test your application for vulnerabilities.

    Platforms like ServerlessBase can help you implement many of these security practices automatically. They handle SSL certificate management, provide secure reverse proxy configurations, and offer built-in security features that reduce the attack surface of your applications.

    Start with the basics: validate input, use HTTPS, and keep your dependencies updated. Then gradually implement more advanced security measures as your application grows and your team becomes more security-conscious.

    Remember: security is a journey, not a destination. Every vulnerability you fix makes your application more secure, and every security practice you implement reduces the risk of a successful attack.

    Leave comment