Insecure Design

Top 10 OWASP


What?

Insecure Design refers to risks stemming from design and architectural flaws. It calls for more use of threat modeling, secure design patterns, and reference architectures. Unlike implementation flaws, insecure design refers to missing or ineffective control design that fails to protect against important security risks. OWASP Software Assurance Maturity Model (SAMM) provides a framework for building secure software.

How to Prevent

  • Establish and use a secure development lifecycle with security professionals
  • Use threat modeling for critical authentication, access control, business logic, and key flows
  • Integrate security language and controls into user stories
  • Use secure design patterns and architectures
  • Write unit and integration tests to validate security controls
  • Segment network and system components
  • Limit resource consumption by user or service
  • Implement failure states securely
  • Define and implement a security baseline
// BAD EXAMPLE - Insecure Business Logic Design
class TransactionService {
  static async transferMoney(fromAccount, toAccount, amount) {
    // No validation of account ownership
    // No transaction atomicity
    // No rate limiting
    await Account.update(
      { balance: fromAccount.balance - amount },
      { where: { id: fromAccount.id } }
    );
    await Account.update(
      { balance: toAccount.balance + amount },
      { where: { id: toAccount.id } }
    );
  }
}

// GOOD EXAMPLE - Secure Design Implementation
class SecureTransactionService {
  static async transferMoney(userId, fromAccountId, toAccountId, amount) {
    // Rate limiting check
    if (await this.isRateLimitExceeded(userId)) {
      throw new Error("Rate limit exceeded");
    }

    // Start transaction for atomicity
    const transaction = await sequelize.transaction();

    try {
      // Verify account ownership
      const fromAccount = await Account.findOne({
        where: { id: fromAccountId, userId },
        lock: true,
        transaction,
      });

      if (!fromAccount) {
        throw new Error("Source account not found or unauthorized");
      }

      // Validate sufficient funds
      if (fromAccount.balance < amount) {
        throw new Error("Insufficient funds");
      }

      // Verify destination account exists
      const toAccount = await Account.findOne({
        where: { id: toAccountId },
        lock: true,
        transaction,
      });

      if (!toAccount) {
        throw new Error("Destination account not found");
      }

      // Perform transfer within transaction
      await Account.update(
        { balance: fromAccount.balance - amount },
        { where: { id: fromAccountId }, transaction }
      );
      await Account.update(
        { balance: toAccount.balance + amount },
        { where: { id: toAccountId }, transaction }
      );

      // Log transaction for audit
      await TransactionLog.create(
        {
          fromAccountId,
          toAccountId,
          amount,
          userId,
          type: "TRANSFER",
        },
        { transaction }
      );

      // Commit transaction
      await transaction.commit();

      // Notify user
      await NotificationService.sendTransferConfirmation(userId, {
        amount,
        toAccount: toAccount.accountNumber,
      });
    } catch (error) {
      // Rollback on any error
      await transaction.rollback();
      throw error;
    }
  }

  static async isRateLimitExceeded(userId) {
    const LIMIT_WINDOW = 24 * 60 * 60 * 1000; // 24 hours
    const MAX_TRANSFERS = 10;

    const recentTransfers = await TransactionLog.count({
      where: {
        userId,
        type: "TRANSFER",
        createdAt: {
          [Op.gte]: new Date(Date.now() - LIMIT_WINDOW),
        },
      },
    });

    return recentTransfers >= MAX_TRANSFERS;
  }
}

// Example of Secure Error Handling Design
class ErrorHandler {
  static handle(error, req, res) {
    // Log error details securely
    Logger.error({
      error: error.message,
      stack: error.stack,
      requestId: req.id,
      userId: req.user?.id,
    });

    // Don't expose internal errors to client
    if (error instanceof BusinessError) {
      // Safe to expose business logic errors
      return res.status(400).json({
        error: error.message,
        code: error.code,
      });
    } else {
      // Generic error for all other cases
      return res.status(500).json({
        error: "An unexpected error occurred",
        requestId: req.id,
      });
    }
  }
}

// API endpoint with secure design patterns
app.post("/api/transfers", authenticate, async (req, res) => {
  try {
    const { fromAccountId, toAccountId, amount } = req.body;

    // Input validation
    if (!fromAccountId || !toAccountId || !amount) {
      throw new BusinessError("Missing required fields");
    }

    if (amount <= 0) {
      throw new BusinessError("Invalid transfer amount");
    }

    // Perform transfer with secure design
    await SecureTransactionService.transferMoney(
      req.user.id,
      fromAccountId,
      toAccountId,
      amount
    );

    res.json({ message: "Transfer completed successfully" });
  } catch (error) {
    ErrorHandler.handle(error, req, res);
  }
});