阿里云主机折上折
  • 微信号
Current Site:Index > Front-end measures to prevent NoSQL injection

Front-end measures to prevent NoSQL injection

Author:Chuan Chen 阅读数:19802人阅读 分类: 前端安全

Basic Principles of NoSQL Injection

NoSQL injection is an attack targeting non-relational databases, where attackers construct malicious inputs to disrupt query logic or gain unauthorized data access. Unlike traditional SQL injection, NoSQL injection may involve JSON injection, operator abuse, or type confusion. For example, in MongoDB, attackers might exploit operators like $where or $ne to bypass authentication:

// Dangerous example: Directly concatenating user input
const query = {
  username: req.body.username,
  password: req.body.password
};
db.users.find(query);

When an attacker submits { "$ne": null } as the password, they may bypass authentication. While the frontend cannot completely prevent such attacks, the following measures can significantly reduce risks.

Input Validation and Type Checking

Strict client-side validation is the first line of defense. Ensure inputs conform to expected formats and types:

function validateLogin(input: { username: string, password: string }) {
  // Check if it's a string and not empty
  if (typeof input.username !== 'string' || input.username.trim() === '') {
    throw new Error('Invalid username');
  }
  
  // Password complexity validation
  const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
  if (!passwordRegex.test(input.password)) {
    throw new Error('Password must contain letters and numbers and be at least 8 characters');
  }
  
  // Prevent JSON object injection
  if (typeof input.password === 'object') {
    throw new Error('Illegal input format');
  }
}

For numeric fields, explicitly convert and validate ranges:

function sanitizePageNumber(rawInput) {
  const num = Number.parseInt(rawInput, 10);
  if (Number.isNaN(num) || num < 1 || num > 100) {
    return 1; // Default value
  }
  return num;
}

Data Serialization and Encoding

Escape special characters before sending data to the backend:

function escapeMongoOperators(input) {
  if (typeof input !== 'string') return input;
  
  const operatorPattern = /(\$[a-z]+|\$exists|\$type|\$not)/gi;
  return input.replace(operatorPattern, (match) => {
    return `\\${match}`;
  });
}

// Usage example
const safeInput = escapeMongoOperators(userInput);

For GraphQL interfaces, use variables instead of string concatenation:

# Wrong approach
query {
  users(filter: "${unsafeFilter}") { ... }

# Correct approach
query($filter: String!) {
  users(filter: $filter) { ... }
}

Content Security Policy (CSP) Configuration

Restrict inline scripts and external resource loading via CSP to reduce XSS-related NoSQL injection risks:

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self';
               script-src 'self' 'unsafe-eval';
               connect-src api.example.com;
               object-src 'none'">

Secure Practices for Frontend ORMs

Use type-safe query builders to automatically defend against injection:

// Mongoose example
import mongoose from 'mongoose';

const UserSchema = new mongoose.Schema({
  username: { type: String, required: true },
  password: { type: String, select: false }
});

UserSchema.statics.safeFind = async function(filters) {
  // Automatically escape special operators
  const sanitized = Object.entries(filters).reduce((acc, [key, value]) => {
    if (typeof value === 'object' && value !== null) {
      throw new Error('Direct query objects are not allowed');
    }
    acc[key] = value;
    return acc;
  }, {});
  
  return this.find(sanitized);
};

Real-Time Input Monitoring and Defense

Detect suspicious inputs dynamically during entry:

const suspiciousPatterns = [
  /\$[a-z]+\(/i,
  /["']\s*:\s*["']/,
  /^\s*\{.*\}\s*$/
];

inputElement.addEventListener('input', (e) => {
  const value = e.target.value;
  if (suspiciousPatterns.some(pattern => pattern.test(value))) {
    showWarning('Suspicious input characters detected');
    e.target.classList.add('input-warning');
  }
});

Error Message Handling

Avoid exposing detailed database errors on the client side:

fetch('/api/login', {
  method: 'POST',
  body: JSON.stringify(credentials)
})
.then(response => {
  if (!response.ok) {
    // Unified error message instead of raw errors
    throw new Error('Authentication failed. Please check your credentials');
  }
  return response.json();
})
.catch(error => {
  showToast(error.message);
});

Security Headers Configuration

Ensure API requests include security headers:

fetch('/api/data', {
  headers: {
    'Content-Type': 'application/json',
    'X-Content-Type-Options': 'nosniff',
    'X-XSS-Protection': '1; mode=block'
  }
});

Frontend Storage Security

Avoid storing raw query data in localStorage:

// Unsafe
localStorage.setItem('queryParams', JSON.stringify(rawFilters));

// Safer approach
const safeFilters = {
  page: Number(rawFilters.page) || 1,
  search: String(rawFilters.search || '').substring(0, 100)
};
localStorage.setItem('queryParams', JSON.stringify(safeFilters));

Testing and Automated Detection

Incorporate security tests in CI pipelines:

// Jest test cases
describe('NoSQL Injection Defense', () => {
  test('Should reject inputs containing operators', () => {
    const maliciousInput = { username: 'admin', password: { $ne: '' } };
    expect(() => validateLogin(maliciousInput)).toThrow();
  });

  test('Should escape MongoDB operators', () => {
    expect(escapeMongoOperators('$where')).toBe('\\$where');
  });
});

Development Environment Security Configuration

Pre-configure security rules in project templates:

// .eslintrc.json
{
  "rules": {
    "no-eval": "error",
    "security/detect-object-injection": "warn",
    "security/detect-non-literal-regexp": "error"
  }
}

Framework-Specific Defense Measures

React example: Use controlled components with auto-escaping:

function SearchForm() {
  const [input, setInput] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    const safeInput = input.replace(/[${}]/g, '');
    fetchResults(safeInput);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={input}
        onChange={(e) => setInput(e.target.value)}
        pattern="[^$}{]+" 
        title="Special characters are not allowed"
      />
    </form>
  );
}

Balancing Performance and Security

Use debouncing for high-frequency input fields:

import { debounce } from 'lodash';

const validateInput = debounce((value) => {
  if (value.includes('$')) {
    highlightSecurityWarning();
  }
}, 300);

inputElement.addEventListener('input', (e) => {
  validateInput(e.target.value);
});

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.