List of security issues

1. Authentication and Authorization Issues

Hard-coded and Weak JWT Secret

The JWT secret is hard-coded and uses a weak, predictable value.

Vulnerable Code:

const JWT_SECRET = 'secret';

Impact: Attackers can easily guess or brute-force the JWT secret Possibility of token forgery, leading to unauthorized access and privilege escalation

OWASP API Security Top 10 2023 Relevance: This issue falls under API2:2023 Broken Authentication. Using a weak, hard-coded secret for JWT signing compromises the entire authentication mechanism.

How to reproduce:

{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiZ3Vlc3QiLCJpYXQiOjE3MjgyODU0NjMsImV4cCI6MTcyODI4OTA2M30.Eg_BK9CJcfln5XhCemnMnyKCuuEpfADfstLrT0XBITY"}

Detailed Explanation: Hard-coding a weak JWT secret is a critical security flaw. JWTs are used to authenticate and authorize users, and the secret is crucial for verifying the token's integrity. A weak secret like 'secret' is easily guessable, allowing attackers to forge valid tokens and impersonate any user, including administrators. This vulnerability essentially breaks the entire authentication and authorization system.

Fixed Code:

import crypto from 'crypto';

const JWT_SECRET = crypto.randomBytes(64).toString('hex');
console.log('Use this generated secret in your secure environment:', JWT_SECRET);

Why this fixes the issue: This code generates a strong, random 64-byte (512-bit) secret key. It's then converted to a hexadecimal string, resulting in a 128-character secret. This approach:

The generated secret should be stored securely (e.g., in environment variables) and not in the source code.

Plaintext Password Storage

User passwords are stored in plaintext within the users array.

Vulnerable Code:

let users = [
  { id: 1, username: 'user1', email: 'user1@gmail.com', password: 'password1', isAdmin: false },
  // ...
];

Impact:

OWASP Top 10 2021 Relevance: This issue falls under A03:2021 - Sensitive Data Exposure. Exposing sensitive information like passwords and tokens on a public page leads to data breaches and unauthorized access.

How to reproduce:

let users = [
  { id: 1, username: 'user1', email: 'user1@gmail.com', password: 'password1', isAdmin: false },
  // ...
];

Detailed Explanation: Storing passwords in plaintext is a severe security risk. If an attacker gains access to the server's memory or database, they immediately have access to all user passwords. This not only compromises the current system but also puts users at risk on other platforms where they might reuse the same password. It violates fundamental security principles and data protection regulations.

Fixed Code:

import bcrypt from 'bcrypt';

const saltRounds = 12;

async function createUser(username, email, password, isAdmin = false) {
  const hashedPassword = await bcrypt.hash(password, saltRounds);
  const newUser = {
    id: users.length + 1,
    username,
    email,
    password: hashedPassword,
    isAdmin
  };
  users.push(newUser);
  return newUser;
}

// Usage
await createUser('user1', 'user1@gmail.com', 'password1');

// For password verification
async function verifyPassword(plainTextPassword, hashedPassword) {
  return await bcrypt.compare(plainTextPassword, hashedPassword);
}

Why this fixes the issue: This solution uses the bcrypt hashing algorithm, which is designed for password hashing and includes:

This approach ensures that even if the database is compromised, the actual passwords remain protected.

Disclosure of Credentials on Home Page

The home page displays usernames, passwords, and bearer tokens in a table.

Vulnerable Code:

<table>
  <tr>
    <th>Username</th>
    <th>Password</th>
    <th>Bearer Token</th>
  </tr>
  <tr>
    <td>user1</td>
    <td>password1</td>
    <td>one</td>
  </tr>
  <!-- ... -->
</table>

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under `API3:2023 Excessive Data Exposure. Exposing sensitive user data, including passwords, without proper authentication leads to mass data breaches.

How to reproduce:

Detailed Explanation: Displaying sensitive information like passwords and bearer tokens on a publicly accessible page is a critical security breach. It allows any visitor to the site to obtain valid credentials, bypassing all authentication mechanisms. This can lead to unauthorized access, account takeovers, and potential escalation of privileges if admin credentials are exposed.

Fixed Code:

<table>
  <tr>
    <th>Username</th>
    <th>Last Login</th>
    <th>Account Status</th>
  </tr>
  <tr>
    <td>user1</td>
    <td>2023-10-01 14:30:00</td>
    <td>Active</td>
  </tr>
  <!-- ... -->
</table>

Why this fixes the issue:

Additionally, implement proper authentication and authorization checks to ensure only authorized users can access this page:

app.get('/dashboard', authenticateToken, (req, res) => {
  // Render dashboard with non-sensitive user information
  const safeUserInfo = users.map(user => ({
    username: user.username,
    lastLogin: user.lastLogin,
    accountStatus: user.accountStatus
  }));
  res.render('dashboard', { users: safeUserInfo });
});

This ensures that sensitive data is never exposed, and only authenticated users can access the dashboard.

2. Information Leakage

Information Leakage via Detailed Error Messages

Detailed error messages, including stack traces, are sent to the client.

Vulnerable Code:

res.status(400).json({
  valid: false,
  error: 'Invalid token',
  message: error.message,
  stack: error.stack
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API5:2023 Security Misconfiguration. Providing overly detailed error messages constitutes a security misconfiguration, as it reveals internal system information that should remain confidential. Proper error handling should ensure that only generic error messages are exposed to end-users, while detailed logs are maintained securely for debugging purposes.

How to reproduce:

POST /api/validateToken HTTP/1.1
Host: vulnapi.testinvicti.com
Content-Type: application/json

{"token":"invalid"}
{"valid":false,"error":"Invalid token","message":"jwt malformed","stack":"JsonWebTokenError: jwt malformed\n    at module.exports [as verify] (C:\\acunetix\\work\\inv-vuln-api\\node_modules\\jsonwebtoken\\verify.js:70:17)\n    at C:\\acunetix\\work\\inv-vuln-api\\app.js:411:25\n    at Layer.handle [as handle_request]
... 

Detailed Explanation: Sending detailed error messages, especially stack traces, to the client is a significant security risk. These messages can reveal sensitive information about the application's structure, libraries used, and potential vulnerabilities. Attackers can use this information to:

Fixed Code:

// Error handling middleware
app.use((error, req, res, next) => {
  console.error('Error:', error);  // Log the full error server-side

  // Send a generic error message to the client
  res.status(error.status || 500).json({
    error: {
      message: 'An error occurred while processing your request.'
    }
  });
});

// Usage in route handlers
app.get('/api/resource', (req, res, next) => {
  try {
    // Resource handling logic
  } catch (error) {
    next(error);  // Pass the error to the error handling middleware
  }
});

Why this fixes the issue: This solution addresses the problem by:

This approach balances the need for detailed logging for developers with the security requirement of not exposing sensitive information to potential attackers.

3. Access Control Vulnerabilities

Insecure Direct Object Reference (IDOR)

The user API endpoint allows access to any user's data without proper authorization checks.

Vulnerable Code:

app.get('/api/users/:id', authenticateToken, (req, res, next) => {
  // ...
  res.json(user);
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API1:2023 Broken Object Level Authorization. It allows authenticated users to access or modify objects that they should not have access to.

How to reproduce:

GET /api/users/1 HTTP/1.1
Host: vulnapi.testinvicti.com
Authorization: Bearer two
{"id":1,"username":"user1","email":"user1@gmail.com","password":"password1","isAdmin":false}

Detailed Explanation: The vulnerability here is that the endpoint only checks if the requester is authenticated, not if they have the right to access the specific user data. This means any authenticated user can access any other user's data by simply changing the :id parameter in the URL. This is a classic example of Insecure Direct Object Reference (IDOR), where the application uses user-supplied input to directly access objects without sufficient access control checks.

Fixed Code:

app.get('/api/users/:id', authenticateToken, (req, res, next) => {
  const requestedUserId = parseInt(req.params.id);
  const requestingUserId = req.user.id; // Assuming authenticateToken middleware sets req.user

  // Check if the requesting user is an admin or requesting their own data
  if (req.user.isAdmin || requestingUserId === requestedUserId) {
    const user = users.find(u => u.id === requestedUserId);
    if (user) {
      // Remove sensitive information before sending
      const { password, ...safeUserData } = user;
      res.json(safeUserData);
    } else {
      res.status(404).json({ error: 'User not found' });
    }
  } else {
    res.status(403).json({ error: 'Unauthorized access' });
  }
});

Why this fixes the issue: This solution addresses the IDOR vulnerability by:

This ensures that users can only access their own data or, in the case of admins, provides a controlled way to access other users' data, significantly reducing the risk of unauthorized data exposure.

Broken Access Control in User Update Endpoint

The user update endpoint allows modification of any user's data without proper authorization checks.

Vulnerable Code:

app.put('/api/users/:id', authenticateToken, (req, res) => {
  // ...
  Object.assign(user, req.body);
  res.json(user);
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API1:2023 Broken Object Level Authorization. It allows authenticated users to modify objects that they should not have access to, potentially escalating their privileges.

How to reproduce:

PUT /api/users/1 HTTP/1.1
Host: vulnapi.testinvicti.com
Authorization: Bearer two
Content-Type: application/json
Content-Length: 91

{"id":1,"username":"user1","email":"user1@gmail.com","password":"password1","isAdmin":true}
{"id":1,"username":"user1","email":"user1@gmail.com","password":"password1","isAdmin":true}

Detailed Explanation: The vulnerability in this endpoint is twofold:

This combination allows any authenticated user to modify any other user's data, including escalating their own privileges by setting isAdmin to true.

Fixed Code:

app.put('/api/users/:id', authenticateToken, (req, res) => {
  const requestedUserId = parseInt(req.params.id);
  const requestingUserId = req.user.id;

  // Check if the requesting user is an admin or updating their own data
  if (req.user.isAdmin || requestingUserId === requestedUserId) {
    const user = users.find(u => u.id === requestedUserId);
    if (user) {
      // Whitelist allowed fields to update
      const allowedUpdates = ['username', 'email', 'password'];
      const updates = {};
      allowedUpdates.forEach(field => {
        if (req.body[field]) {
          updates[field] = req.body[field];
        }
      });

      // Special handling for password updates
      if (updates.password) {
        updates.password = bcrypt.hashSync(updates.password, 10);
      }

      // Apply updates
      Object.assign(user, updates);

      // Remove sensitive information before sending response
      const { password, ...safeUserData } = user;
      res.json(safeUserData);
    } else {
      res.status(404).json({ error: 'User not found' });
    }
  } else {
    res.status(403).json({ error: 'Unauthorized access' });
  }
});

Why this fixes the issue: This solution addresses both vulnerabilities by:

These changes ensure that users can only modify their own data (unless they're an admin) and can't escalate their privileges or modify sensitive fields, significantly improving the security of the user update process.

Missing Authorization in Order Deletion

The order deletion endpoint lacks proper authorization checks.

Vulnerable Code:

// Authorization check is commented out
// if (orders[orderIndex].userId !== req.user.userId) {
//   return res.status(403).json({ error: 'Unauthorized to delete this order' });
// }

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API5:2023 Broken Function Level Authorization. Without proper authorization checks on the order deletion endpoint, unauthorized users can perform deletion operations, potentially leading to data loss or unauthorized data manipulation.

How to reproduce:

GET /api/orders/1 HTTP/1.1
Host: vulnapi.testinvicti.com
Authorization: Bearer two
{"id":1,"userId":1,"product":"Widget","quantity":5}
DELETE /api/orders/1 HTTP/1.1
Host: vulnapi.testinvicti.com
Authorization: Bearer two
Content-Type: application/json
{"message":"Order deleted successfully"}

Detailed Explanation: The vulnerability here is the complete lack of authorization checks for order deletion. Even if authentication is in place (assumed from the req.user reference), any authenticated user can delete any order in the system. This is a severe violation of the principle of least privilege and can lead to significant business disruption and data loss.

Fixed Code:

app.delete('/api/orders/:id', authenticateToken, (req, res) => {
  const orderId = parseInt(req.params.id);
  const orderIndex = orders.findIndex(order => order.id === orderId);

  if (orderIndex === -1) {
    return res.status(404).json({ error: 'Order not found' });
  }

  // Check if the user is authorized to delete this order
  if (orders[orderIndex].userId !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Unauthorized to delete this order' });
  }

  // Perform the deletion
  const deletedOrder = orders.splice(orderIndex, 1)[0];
  
  // Log the deletion for audit purposes
  console.log(`Order ${orderId} deleted by user ${req.user.id} at ${new Date().toISOString()}`);

  res.json({ message: 'Order deleted successfully', order: deletedOrder });
});

Why this fixes the issue: This solution addresses the authorization vulnerability by:

These changes ensure that only authorized users (order owners or admins) can delete orders, maintaining data integrity and preventing unauthorized manipulations.

4. Data Exposure

Vulnerable Code:

app.get('/api/v1/users', (req, res) => {
  res.json(users);
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API2:2023 Broken User Authentication. The absence of authentication on the API endpoint allows unauthorized access to sensitive user data, including passwords, thereby compromising the entire authentication mechanism.

How to reproduce:

GET /api/v1/users HTTP/1.1
Host: vulnapi.testinvicti.com
[
  {"id":1,"username":"user1","email":"user1@gmail.com","password":"password1","isAdmin":false},
...
]

Detailed Explanation: This endpoint is critically vulnerable as it exposes all user data, including sensitive information like passwords, without any form of authentication or authorization. This is a severe violation of user privacy and data protection principles. It allows anyone with access to the API to retrieve all user information, potentially leading to account takeovers, identity theft, and other malicious activities.

Fixed Code:

app.get('/api/v1/users', authenticateToken, (req, res) => {
  if (!req.user.isAdmin) {
    return res.status(403).json({ error: 'Unauthorized access' });
  }

  // Only return necessary user information, excluding sensitive data
  const safeUserData = users.map(user => ({
    id: user.id,
    username: user.username,
    email: user.email,
    isAdmin: user.isAdmin
    // Add other non-sensitive fields as needed
  }));

  res.json(safeUserData);
});

Why this fixes the issue: This solution addresses the data exposure vulnerability by:

These changes ensure that sensitive user data is protected, access is restricted to authorized personnel, and the principle of least privilege is upheld. It also helps in complying with data protection regulations by limiting unnecessary data exposure.

5. Server-Side Request Forgery (SSRF)

Server-Side Request Forgery (SSRF)

An API endpoint allows arbitrary URL fetching without proper validation.

Vulnerable Code:

app.get('/api/fetch', authenticateToken, async (req, res) => {
  const response = await axios.get(req.query.url);
  res.json(response.data);
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue directly corresponds to API7:2023 Server Side Request Forgery. It allows an attacker to induce the server to make requests to an arbitrary destination.

How to reproduce:

GET /api/fetch?url=http://bxss.me/ HTTP/1.1
Authorization: Bearer one
Host: vulnapi.testinvicti.com
"<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>AcuMonitor</title>\r\n</head>\r\n<body>\r\n<script>window.location.replace(\"https://www.acunetix.com/vulnerability-scanner/acumonitor-technology/\");</script>\r\n</body>\r\n</html>"

Detailed Explanation: This endpoint is vulnerable to SSRF attacks because it allows a client to specify any URL to be fetched by the server. An attacker can exploit this to:

Fixed Code:

const url = require('url');
const net = require('net');

app.get('/api/fetch', authenticateToken, async (req, res) => {
  try {
    const targetUrl = new URL(req.query.url);

    // Whitelist of allowed domains
    const allowedDomains = ['api.example.com', 'public-api.trusted-domain.com'];
    
    if (!allowedDomains.includes(targetUrl.hostname)) {
      return res.status(403).json({ error: 'Access to this domain is not allowed' });
    }

    // Check if the hostname resolves to a private IP
    const ipAddress = await new Promise((resolve) => {
      net.lookup(targetUrl.hostname, (err, address) => {
        if (err) {
          resolve(null);
        } else {
          resolve(address);
        }
      });
    });

    if (ipAddress) {
      const octets = ipAddress.split('.');
      if (octets[0] === '10' || 
          (octets[0] === '172' && octets[1] >= 16 && octets[1] <= 31) || 
          (octets[0] === '192' && octets[1] === '168')) {
        return res.status(403).json({ error: 'Access to private IP addresses is not allowed' });
      }
    }

    const response = await axios.get(targetUrl.toString(), { 
      timeout: 5000,
      maxRedirects: 5
    });
    
    res.json(response.data);
  } catch (error) {
    console.error('Error fetching URL:', error);
    res.status(500).json({ error: 'An error occurred while fetching the URL' });
  }
});

Why this fixes the issue: This solution addresses the SSRF vulnerability by:

These measures significantly reduce the risk of SSRF attacks by limiting the scope of allowed requests and preventing access to internal resources.

6. Privilege Escalation

Unauthenticated User Creation with Privilege Escalation

The user creation endpoint allows setting admin privileges without proper checks.

Vulnerable Code:

app.post('/api/users', (req, res) => {
  const { username, password, isAdmin } = req.body;
  // ...
  const newUser = { id: users.length + 1, username, password, isAdmin: isAdmin || false };
  // ...
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue relates to API5:2023 Broken Function Level Authorization. It allows unauthorized elevation of privileges during user creation.

How to reproduce:

POST /api/users HTTP/1.1
Host: vulnapi.testinvicti.com
Content-Type: application/json
Content-Length: 93

{"username":"usertest","email":"usertest@gmail.com","password":"passwordtest","isAdmin":true}
{
  "id": 2,
  "username": "usertest",
  "email": "usertest@gmail.com",
  "isAdmin": true
}

Detailed Explanation: This endpoint is critically vulnerable because:

Fixed Code:

app.post('/api/users', authenticateToken, (req, res) => {
  // Only allow admin users to access this endpoint
  if (!req.user.isAdmin) {
    return res.status(403).json({ error: 'Unauthorized: Admin access required' });
  }

  const { username, password, email } = req.body;

  // Validate input
  if (!username || !password || !email) {
    return res.status(400).json({ error: 'Username, password, and email are required' });
  }

  // Check if username or email already exists
  if (users.some(u => u.username === username || u.email === email)) {
    return res.status(409).json({ error: 'Username or email already exists' });
  }

  // Create new user (always non-admin)
  const newUser = {
    id: users.length + 1,
    username,
    email,
    password: bcrypt.hashSync(password, 10), // Hash the password
    isAdmin: false // New users are always non-admin
  };

  users.push(newUser);

  // Log user creation
  console.log(`New user created: ${username} (ID: ${newUser.id}) by admin ${req.user.username} at ${new Date().toISOString()}`);

  // Return user data without password
  const { password: _, ...userWithoutPassword } = newUser;
  res.status(201).json(userWithoutPassword);
});

Why this fixes the issue: This solution addresses the privilege escalation vulnerability by:

These changes ensure that the user creation process is secure, traceable, and doesn't allow unauthorized privilege escalation.

7. Environment-Specific Vulnerabilities

Missing Authentication in Admin Routes on Dev Environment

Admin routes bypass authentication checks in the development environment.

Vulnerable Code:

app.get('/api/admin/users', (req, res, next) => {
  if (req.hostname === 'dev') {
    next();
  } else {
    authenticateToken(req, res, () => {
      isAdmin(req, res, next);
    });
  }
}, (req, res) => {
  res.json(users);
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue relates to API2:2023 Broken Authentication and API5:2023 Broken Function Level Authorization. It bypasses authentication and authorization checks in a specific environment.

How to reproduce:

GET /api/admin/users HTTP/1.1
Host: dev

[{"id":1,"username":"user1","email":"user1@gmail.com","password":"password1","isAdmin":false},
...
]

Detailed Explanation: This code introduces a severe security vulnerability by bypassing authentication and authorization checks in the development environment. The intention might have been to facilitate easier testing, but it creates a significant security risk:

Fixed Code:

const isDevEnvironment = process.env.NODE_ENV === 'development';

// Middleware for development logging (if needed)
const devLogging = (req, res, next) => {
  if (isDevEnvironment) {
    console.log(`Admin route accessed: ${req.method} ${req.path}`);
  }
  next();
};

app.get('/api/admin/users', devLogging, authenticateToken, isAdmin, (req, res) => {
  // Remove sensitive information before sending
  const safeUserData = users.map(({ password, ...user }) => user);
  res.json(safeUserData);
});

// If easier access is needed in development, use a dedicated dev admin account
if (isDevEnvironment) {
  console.log('Development environment detected. Creating dev admin account.');
  const devAdminPassword = crypto.randomBytes(16).toString('hex');
  users.push({
    id: 0,
    username: 'dev_admin',
    password: bcrypt.hashSync(devAdminPassword, 10),
    isAdmin: true
  });
  console.log(`Dev admin credentials: dev_admin / ${devAdminPassword}`);
}

Why this fixes the issue: This solution addresses the environment-specific vulnerability by:

These changes maintain security integrity across all environments while still providing the necessary tools for development and testing.

8. Sensitive Data Exposure

Exposure of Application Logs Without Authentication

An endpoint exposes application logs without proper authentication.

Vulnerable Code:

app.get('/api/admin/logs', (req, res) => {
  res.json(logs);
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API7:2023 Security Misconfiguration. Exposing application logs without proper authentication indicates a misconfiguration that allows unauthorized access to sensitive internal information, potentially leading to information disclosure and aiding attackers in exploiting other vulnerabilities.

How to reproduce:

GET /api/admin/logs HTTP/1.1
Host: vulnapi.testinvicti.com

[
  {"timestamp":"2023-08-29T10:00:00Z","level":"INFO","message":"Application started"},{"timestamp":"2023-08-29T10:05:23Z","level":"DEBUG","message":"User login attempt"},
...
]

Detailed Explanation: This endpoint is highly vulnerable because it exposes application logs without any authentication or authorization checks. Application logs often contain sensitive information such as:

Fixed Code:

app.get('/api/admin/logs', authenticateToken, isAdmin, (req, res) => {
  try {
    // Pagination
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 100;
    const startIndex = (page - 1) * limit;
    const endIndex = page * limit;

    // Filter sensitive information
    const sanitizedLogs = logs.map(log => ({
      timestamp: log.timestamp,
      level: log.level,
      message: log.message.replace(/(\b\d{4}\b(-?\d{4}){3})/g, '****-****-****-****') // Mask potential credit card numbers
                           .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, '[EMAIL REDACTED]') // Mask email addresses
    }));

    const paginatedLogs = sanitizedLogs.slice(startIndex, endIndex);

    res.json({
      totalLogs: sanitizedLogs.length,
      page: page,
      totalPages: Math.ceil(sanitizedLogs.length / limit),
      logs: paginatedLogs
    });
  } catch (error) {
    console.error('Error retrieving logs:', error);
    res.status(500).json({ error: 'An error occurred while retrieving logs' });
  }
});

// Implement log rotation and retention policy
const fs = require('fs');
const path = require('path');

function rotateLogsDaily() {
  const today = new Date().toISOString().split('T')[0];
  const logFile = path.join(__dirname, 'logs', `app-${today}.log`);
  
  // Implement log writing logic here
  // ...

  // Delete logs older than 30 days
  const logDir = path.join(__dirname, 'logs');
  fs.readdir(logDir, (err, files) => {
    if (err) throw err;
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - 30);

    files.forEach(file => {
      const filePath = path.join(logDir, file);
      fs.stat(filePath, (err, stats) => {
        if (err) throw err;
        if (stats.mtime < cutoffDate) {
          fs.unlink(filePath, err => {
            if (err) console.error(`Error deleting old log file ${file}:`, err);
            else console.log(`Deleted old log file: ${file}`);
          });
        }
      });
    });
  });
}

// Run log rotation daily
setInterval(rotateLogsDaily, 24 * 60 * 60 * 1000);

Why this fixes the issue: This solution addresses the sensitive data exposure vulnerability by:

These measures significantly reduce the risk of sensitive data exposure while still providing necessary logging functionality for authorized administrators.

Sensitive Data Exposure via Metrics Endpoint

An endpoint exposes sensitive API keys and other metrics without proper authentication.

Vulnerable Code:

app.get('/api/metrics', (req, res) => {
  res.json({
    // ...
    apiKeys: {
      stripe_production: "pk_live_...",
      // ...
    },
  });
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue relates to API3:2023 Excessive Data Exposure. Exposing sensitive configuration data, such as API keys, without proper access controls leads to significant security risks.

How to reproduce:

GET /api/metrics HTTP/1.1
Host: vulnapi.testinvicti.com
{
...
"databaseVersion":"PostgreSQL 14.5",
"apiKeys":{"stripe_production":"pk_live_2xukkscb7yfearcm2zk1unnw",
"stripe_test":"pk_test_qrstuvwxiz67890abcdef",
"openai_api_key":"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",
"anthropic_api_key":"sk-ant-api03-AwaO6SeeIdWbZoV39HW6i4ZH7unuKidYFPrylA1L5B0ddz_9oKFGPFWkIph5j_pFDn0hGMFYanDx83j3i1YmIQ-5OKQQgAA"}
}

Detailed Explanation: This endpoint is critically vulnerable because it exposes sensitive API keys and potentially other confidential metrics without any authentication or authorization. Exposing API keys, especially production keys for payment services like Stripe, can lead to:

Fixed Code:

const rateLimit = require("express-rate-limit");

// Create a rate limiter
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});

app.get('/api/metrics', authenticateToken, isAdmin, apiLimiter, (req, res) => {
  // Fetch real-time metrics
  const currentMetrics = {
    userCount: users.length,
    activeUsers: users.filter(u => u.lastActivity > Date.now() - 3600000).length, // active in last hour
    totalOrders: orders.length,
    revenueLastWeek: orders.filter(o => o.date > Date.now() - 7 * 24 * 3600000)
                           .reduce((sum, order) => sum + order.total, 0)
  };

  // Log access to metrics
  console.log(`Metrics accessed by admin ${req.user.username} at ${new Date().toISOString()}`);

  res.json(currentMetrics);
});

// Separate endpoint for system health check (no authentication required)
app.get('/api/health', (req, res) => {
  res.json({
    status: 'healthy',
    uptime: process.uptime(),
    timestamp: Date.now()
  });
});

Why this fixes the issue: This solution addresses the sensitive data exposure vulnerability by:

For handling API keys and other sensitive configuration:

const stripeKey = process.env.STRIPE_API_KEY;

These changes ensure that sensitive data is protected while still providing necessary metrics and health check functionalities.

9. File System Vulnerabilities

Path Traversal in Avatar Endpoint

The avatar endpoint allows arbitrary file access through path traversal.

Vulnerable Code:

app.get('/api/avatars/:fname', (req, res) => {
  const filePath = `./uploads/${req.params.fname}`;
  // ...
  res.sendFile(filePath, options, (err) => {
    // ...
  });
});

Impact:

OWASP Top 10 2021 Relevance: This issue falls under A01:2021 - Broken Access Control. Path traversal vulnerabilities enable attackers to bypass access controls and access unauthorized files or directories, compromising the application's integrity and confidentiality.

How to reproduce:

GET /api/avatars/%2e%2e%2fapp.js HTTP/1.1
Host: vulnapi.testinvicti.com
const express = require('express');
const path = require('path');
const jwt = require('jsonwebtoken');
const axios = require('axios');
const swaggerUi = require('swagger-ui-express');
...

Detailed Explanation: This endpoint is vulnerable to path traversal attacks because it directly uses user input to construct the file path without proper validation or sanitization. An attacker can exploit this by using ../ sequences in the fname parameter to navigate outside the intended directory. For example, a request to /api/avatars/../../../etc/passwd could potentially expose the system's password file on a Unix-based system.

Fixed Code:

const path = require('path');
const fs = require('fs').promises;
const mime = require('mime-types'); // For MIME type detection

app.get('/api/avatars/:fname', async (req, res) => {
  try {
    // Sanitize and validate the filename
    const sanitizedFilename = path.basename(req.params.fname);

    // Construct the full path, ensuring it's within the uploads directory
    const uploadDir = path.resolve(__dirname, 'uploads');
    const filePath = path.resolve(uploadDir, sanitizedFilename);

    // Ensure the file is within the upload directory
    if (!filePath.startsWith(uploadDir + path.sep)) {
      return res.status(403).json({ error: 'Access denied' });
    }

    // Check if file exists and is readable
    await fs.access(filePath, fs.constants.R_OK);

    // Get the MIME type
    const mimeType = mime.lookup(filePath);
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!allowedTypes.includes(mimeType)) {
      return res.status(403).json({ error: 'Invalid file type' });
    }

    // Set proper content type
    res.type(mimeType);

    // Stream the file
    res.sendFile(filePath, (err) => {
      if (err) {
        console.error('Error sending file:', err);
        res.status(err.status || 500).end();
      }
    });
  } catch (error) {
    console.error('Error accessing avatar:', error);
    res.status(404).json({ error: 'Avatar not found' });
  }
});

Why this fixes the issue: This solution addresses the path traversal vulnerability by:

10. SQL Injection

SQL Injection in Product Retrieval

The product retrieval endpoint is vulnerable to SQL injection.

Vulnerable Code:

app.get('/api/admin/products/:id', authenticateToken, (req, res) => {
  const query = `SELECT * FROM products WHERE id = ${req.params.id}`;
  db.get(query, (err, row) => {
    // ...
  });
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API8:2023 Injection. SQL injection vulnerabilities allow attackers to manipulate database queries, potentially accessing or modifying sensitive data.

How to reproduce:

GET /api/admin/products/-1%20union%20select%20sqlite_version(),1,1 HTTP/1.1
Authorization: Bearer one
Host: vulnapi.testinvicti.com
{"id":"3.44.2","name":1,"price":1}

Detailed Explanation: This endpoint is highly vulnerable to SQL injection attacks because it directly interpolates user input into the SQL query string. An attacker can exploit this by injecting malicious SQL code into the id parameter. For example, an input like 1 OR 1=1 would return all products, while 1; DROP TABLE products;-- could delete the entire products table.

Fixed Code:

const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('./database.sqlite');

app.get('/api/admin/products/:id', authenticateToken, isAdmin, (req, res) => {
  const productId = parseInt(req.params.id, 10);

  if (isNaN(productId)) {
    return res.status(400).json({ error: 'Invalid product ID' });
  }

  const query = 'SELECT * FROM products WHERE id = ?';
  
  db.get(query, [productId], (err, row) => {
    if (err) {
      console.error('Database error:', err);
      return res.status(500).json({ error: 'An error occurred while retrieving the product' });
    }
    
    if (!row) {
      return res.status(404).json({ error: 'Product not found' });
    }
    
    res.json(row);
  });
});

// Implement proper error handling for the database
db.on('error', (err) => {
  console.error('Database error:', err);
});

// Ensure the database is properly closed when the application shuts down
process.on('SIGINT', () => {
  db.close((err) => {
    if (err) {
      console.error('Error closing database:', err);
    } else {
      console.log('Database connection closed');
    }
    process.exit(err ? 1 : 0);
  });
});

Why this fixes the issue: This solution addresses the SQL injection vulnerability by:

These measures prevent SQL injection attacks by ensuring that user input is treated as data, not executable code, in the context of the SQL query.

11. Token Management

Use of Hard-Coded Tokens for Authentication

The application uses hard-coded tokens for authentication.

Vulnerable Code:

const tokens = {
  'one': { userId: 1, username: 'user1' },
  // ...
};

Impact:

OWASP API Security Top 10 2023 Relevance: This issue relates to API2:2023 Broken Authentication. The use of hard-coded tokens severely undermines the security of the authentication system.

How to reproduce:

GET /api/admin/products/1 HTTP/1.1
Authorization: Bearer one
Host: vulnapi.testinvicti.com

Detailed Explanation: Using hard-coded tokens for authentication is a critical security vulnerability because:

Fixed Code:

const crypto = require('crypto');
const jwt = require('jsonwebtoken');

// Use a strong, randomly generated secret for JWT signing
const JWT_SECRET = crypto.randomBytes(64).toString('hex');

// Store active refresh tokens (consider using Redis for distributed systems)
const activeRefreshTokens = new Set();

// User authentication function
async function authenticateUser(username, password) {
  // Implement proper password checking here (e.g., bcrypt comparison)
  const user = users.find(u => u.username === username);
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return null;
  }
  return user;
}

// Token generation
function generateTokens(user) {
  const accessToken = jwt.sign(
    { userId: user.id, username: user.username },
    JWT_SECRET,
    { expiresIn: '15m' }
  );
  
  const refreshToken = crypto.randomBytes(40).toString('hex');
  activeRefreshTokens.add(refreshToken);

  return { accessToken, refreshToken };
}

// Login endpoint
app.post('/api/login', async (req, res) => {
  const { username, password } = req.body;
  const user = await authenticateUser(username, password);

  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const { accessToken, refreshToken } = generateTokens(user);

  // Set refresh token in HTTP-only cookie
  res.cookie('refreshToken', refreshToken, { 
    httpOnly: true, 
    secure: process.env.NODE_ENV === 'production', 
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
  });

  res.json({ accessToken });
});

// Token refresh endpoint
app.post('/api/refresh-token', (req, res) => {
  const refreshToken = req.cookies.refreshToken;

  if (!refreshToken || !activeRefreshTokens.has(refreshToken)) {
    return res.status(401).json({ error: 'Invalid refresh token' });
  }

  try {
    const user = jwt.verify(refreshToken, JWT_SECRET);
    const { accessToken, refreshToken: newRefreshToken } = generateTokens(user);

    // Invalidate old refresh token
    activeRefreshTokens.delete(refreshToken);

    // Set new refresh token in HTTP-only cookie
    res.cookie('refreshToken', newRefreshToken, { 
      httpOnly: true, 
      secure: process.env.NODE_ENV === 'production', 
      sameSite: 'strict',
      maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
    });

    res.json({ accessToken });
  } catch (error) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

// Logout endpoint
app.post('/api/logout', (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  
  if (refreshToken) {
    activeRefreshTokens.delete(refreshToken);
  }

  res.clearCookie('refreshToken');
  res.json({ message: 'Logged out successfully' });
});

// Middleware to verify access token
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'Access token required' });
  }

  jwt.verify(token, JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Invalid or expired token' });
    }
    req.user = user;
    next();
  });
}

Why this fixes the issue: This solution addresses the token management vulnerability by:

These changes significantly improve the security of the authentication system by making tokens unpredictable, short-lived, and revocable, while also providing a secure way for users to maintain longer sessions.

12. Inconsistent Authentication

Inconsistent Authentication Mechanisms

The application uses both JWT tokens and hard-coded tokens in different parts of the application.

Impact:

OWASP API Security Top 10 2023 Relevance: This issue relates to API2:2023 Broken Authentication and API7:2023 Security Misconfiguration. Inconsistent authentication mechanisms can lead to security gaps and potential vulnerabilities.

How to reproduce:

Detailed Explanation: Having inconsistent authentication mechanisms across an application is a significant security risk because:

Fixed Code: Instead of showing specific code, here's a strategy to fix this issue:

// authMiddleware.js
const jwt = require('jsonwebtoken');
const { JWT_SECRET } = require('./config');

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'Access token required' });
  }

  jwt.verify(token, JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Invalid or expired token' });
    }
    req.user = user;
    next();
  });
}

module.exports = { authenticateToken };

Use this middleware consistently across all protected routes:

const { authenticateToken } = require('./authMiddleware');

app.get('/api/protected-resource', authenticateToken, (req, res) => {
  // Protected route logic
});

app.post('/api/admin/action', authenticateToken, isAdmin, (req, res) => {
  // Admin-only route logic
});

Implement role-based access control (RBAC) for different levels of authorization:

function isAdmin(req, res, next) {
  if (req.user && req.user.role === 'admin') {
    next();
  } else {
    res.status(403).json({ error: 'Admin access required' });
  }
}

// Usage
app.post('/api/admin/action', authenticateToken, isAdmin, (req, res) => {
  // Admin-only action
});
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    logger.warn(`Authentication failure: ${err.message}`);
    return res.status(401).json({ error: 'Invalid token' });
  }
  next(err);
});

Why this fixes the issue: This approach addresses the inconsistent authentication problem by:

By implementing these changes, the application's authentication becomes consistent, more secure, and easier to manage, significantly reducing the risk of authentication-related vulnerabilities.

13. Data Protection

Returning Passwords in API Responses

User objects returned by API endpoints include password fields.

Vulnerable Code:

res.json(user); // 'user' object includes the 'password' field

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API3:2023 Excessive Data Exposure. Including password fields in API responses unnecessarily exposes sensitive information, increasing the risk of credential compromise.

How to reproduce:

GET /api/users/1 HTTP/1.1
Host: vulnapi.testinvicti.com
Authorization: Bearer one
{"id":1,"username":"user1","email":"user1@gmail.com","password":"password1","isAdmin":false}

Detailed Explanation: Returning password fields in API responses, even if they are hashed, is a severe security risk because:

Fixed Code:

// User retrieval function
function getSafeUser(user) {
  const { password, ...safeUser } = user;
  return safeUser;
}

// API endpoint
app.get('/api/users/:id', authenticateToken, (req, res) => {
  const userId = parseInt(req.params.id);
  const user = users.find(u => u.id === userId);

  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  // Check if the requesting user has permission to view this user's data
  if (req.user.id !== userId && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Unauthorized access' });
  }

  res.json(getSafeUser(user));
});

// For bulk user retrieval
app.get('/api/users', authenticateToken, isAdmin, (req, res) => {
  const safeUsers = users.map(getSafeUser);
  res.json(safeUsers);
});

Why this fixes the issue: This solution addresses the data protection vulnerability by:

Additional security measures:

These changes ensure that sensitive data like passwords are never exposed via API responses, significantly reducing the risk of credential exposure and improving compliance with data protection best practices.

14. Cross-Site Scripting (XSS)

Cross-Site Scripting (XSS) via Proxy Endpoint

The proxy endpoint returns unsanitized data in an HTML context.

Vulnerable Code:

res.send(`<div>${response.data}</div>`);

Impact:

OWASP API Security Top 10 2023 Relevance: This issue falls under API8:2023 - Injection. Returning unsanitized data allows attackers to inject malicious scripts into the HTML context, leading to Cross-Site Scripting vulnerabilities.

How to reproduce:

GET /api/proxy?url=http://bxss.me/t/xss.html HTTP/1.1
Authorization: Bearer one
Host: vulnapi.testinvicti.com
<div><script>prompt(98589956)</script></div>

Detailed Explanation: This endpoint is vulnerable to XSS attacks because it directly inserts unvalidated and unsanitized data into the HTML response. If the response.data contains malicious JavaScript, it will be executed in the user's browser context. This can lead to:

Fixed Code:

const express = require('express');
const axios = require('axios');
const { JSDOM } = require('jsdom');
const createDOMPurify = require('dompurify');

const app = express();

// Create DOMPurify instance
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

app.get('/api/proxy', async (req, res) => {
  try {
    const response = await axios.get(req.query.url);
    
    // Sanitize the response data
    const sanitizedData = DOMPurify.sanitize(response.data);
    
    // Set proper Content-Type header
    res.setHeader('Content-Type', 'text/html');
    
    // Send sanitized data
    res.send(`<div>${sanitizedData}</div>`);
  } catch (error) {
    console.error('Proxy error:', error);
    res.status(500).send('An error occurred while fetching the data');
  }
});

// Implement Content Security Policy
app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
  );
  next();
});

Why this fixes the issue: This solution addresses the XSS vulnerability by:

Additional best practices:

These measures significantly reduce the risk of XSS attacks by ensuring that any data returned from the API is properly sanitized before being inserted into the HTML context.

15. Denial of Service (DoS)

Potential Denial of Service (DoS) via Resource Exhaustion

An endpoint returns a large amount of data without pagination or limits.

Vulnerable Code:

app.get('/api/data', authenticateToken, (req, res) => {
  const hugeData = new Array(1500).fill('data');
  res.json(hugeData);
});

Impact:

OWASP API Security Top 10 2023 Relevance: This issue relates to API4:2023 Unrestricted Resource Consumption. It allows an attacker to exhaust server resources by making requests that return large amounts of data.

How to reproduce:

import requests
import threading
import time

def send_request(thread_id):
    url = "http://vulnapi.testinvicti.com/api/data"
    try:
        response = requests.get(url)
        data_size = len(response.content)
        print(f"Thread {thread_id}: Received {data_size} bytes")
    except requests.RequestException as e:
        print(f"Thread {thread_id}: Error - {e}")

def main():
    num_threads = 10  # Number of concurrent threads
    num_requests_per_thread = 50  # Number of requests each thread will make
    threads = []

    start_time = time.time()

    for i in range(num_threads):
        thread = threading.Thread(target=lambda: [send_request(i) for _ in range(num_requests_per_thread)])
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()

    end_time = time.time()
    print(f"Total execution time: {end_time - start_time:.2f} seconds")
    print(f"Total requests sent: {num_threads * num_requests_per_thread}")

if __name__ == "__main__":
    main()

Detailed Explanation: This endpoint is vulnerable to DoS attacks because:

Fixed Code:

const express = require('express');
const rateLimit = require('express-rate-limit');

const app = express();

// Implement rate limiting
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});

app.use('/api/', apiLimiter);

// Paginated data endpoint
app.get('/api/data', authenticateToken, (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 50;
  const startIndex = (page - 1) * limit;
  const endIndex = page * limit;

  // Simulate fetching data from a database
  const allData = new Array(1500).fill().map((_, index) => ({ id: index + 1, data: `Item ${index + 1}` }));

  const paginatedData = allData.slice(startIndex, endIndex);

  const response = {
    data: paginatedData,
    currentPage: page,
    totalPages: Math.ceil(allData.length / limit),
    totalItems: allData.length
  };

  if (endIndex < allData.length) {
    response.nextPage = page + 1;
  }

  if (startIndex > 0) {
    response.previousPage = page - 1;
  }

  res.json(response);
});

// Implement request timeout
app.use((req, res, next) => {
  req.setTimeout(5000, () => {
    res.status(408).send('Request Timeout');
  });
  next();
});

Why this fixes the issue: This solution addresses the potential DoS vulnerability by:

Additional best practices:

These measures significantly reduce the risk of DoS attacks by limiting resource consumption, improving response efficiency, and providing mechanisms to handle large datasets without overwhelming the server.