Skip to main content

Overview

This vulnerability occurs when an application relies on client-side controls (e.g., JavaScript validation, hidden form fields, disabled buttons, client-side permission checks) to enforce security rules, rather than performing authoritative checks on the server-side. Attackers can easily bypass client-side controls by modifying the HTML/JavaScript in their browser, intercepting and modifying requests with a proxy (like Burp Suite), or crafting raw HTTP requests directly to the server. 💻➡️🧍‍♂️➡️🔥

Business Impact

Relying on client-side security leads to critical vulnerabilities:
  • Authorization Bypass: Attackers can modify hidden fields or JavaScript checks to gain access to functions or data intended for administrators or other users.
  • Data Tampering: Prices in shopping carts, target account numbers for transfers, or user roles can be modified before submission, leading to fraud or unauthorized changes.
  • Input Validation Bypass: Constraints enforced only by JavaScript (e.g., length limits, character restrictions) can be bypassed, leading to injection attacks or data corruption if the server doesn’t re-validate.
Rule of Thumb: Never trust the client. All security decisions and validations must be enforced server-side.

Reference Details

CWE ID: CWE-602 OWASP Top 10 (2021): A04:2021 - Insecure Design Severity: High to Critical

Framework-Specific Analysis and Remediation

This is a fundamental design flaw, independent of specific frameworks, although frameworks provide the tools for server-side validation which must be used. Client-side validation is useful for improving user experience (providing immediate feedback) but must never be the only line of defense. Key Remediation Principles:
  1. Duplicate Validation: Perform all critical validation checks (type, format, range, business rules) on the server, even if they are already done on the client.
  2. Server Authority: Base security decisions (permissions, pricing, targets) only on trusted server-side data (e.g., user session, database records), not on hidden fields or parameters submitted by the client.
  3. Secure Session Management: Store sensitive user state (like role, ID) securely in server-side sessions or signed/encrypted tokens, not in client-modifiable locations.

  • Python
  • Java
  • .NET(C#)
  • PHP
  • Node.js
  • Ruby

Framework Context

Relying on JavaScript form validation or hidden fields in HTML forms without equivalent checks in Django views/forms or Flask routes.

Vulnerable Scenario 1: Hidden Price Field

A shopping cart form uses a hidden field for the item price, validated only by JavaScript.
<form action="/checkout" method="post">
  {% csrf_token %}
  <p>Item: T-Shirt</p>
  <p>Price: $10.00</p>
  <input type="hidden" name="item_id" value="tshirt-01">
  <input type="hidden" name="price" value="10.00" id="item-price">
  <button type="submit">Checkout</button>
</form>
# views/checkout.py (Django)
@login_required
def process_checkout(request):
    if request.method == 'POST':
        item_id = request.POST.get('item_id')
        # DANGEROUS: Trusting the price sent from the client's hidden field.
        # Attacker can change this value to 0.01 using browser dev tools.
        price = Decimal(request.POST.get('price', '0.00'))
        # ... charge user based on the submitted 'price' ...
        charge_user(request.user, price) # Charges potentially incorrect amount
        return HttpResponse("Charged!")
    # ...

Vulnerable Scenario 2: Client-Side Admin Check

JavaScript hides an admin button, but the server endpoint doesn’t re-verify admin privileges.
<button id="delete-users-btn" style="display: none;">Delete All Users</button>
<script>
  // DANGEROUS: Security check only happens client-side.
  if (currentUser.isAdmin) {
    document.getElementById('delete-users-btn').style.display = 'block';
    document.getElementById('delete-users-btn').onclick = () => {
      fetch('/api/admin/delete-all-users', { method: 'POST' }); // Assume CSRF handled via header
    };
  }
</script>
# api/admin_views.py (Django/DRF)
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated # Missing IsAdminUser!

@api_view(['POST'])
@permission_classes([IsAuthenticated]) # DANGEROUS: Only checks if logged in, not role.
def delete_all_users(request):
    # Attacker bypasses JS and sends request directly.
    # Server executes this because IsAuthenticated passes.
    User.objects.all().delete()
    return Response(status=204)

Mitigation and Best Practices

  • Prices/Critical Data: Always retrieve prices, product details, and permissions from the server-side (database) based on the item ID or user session after submission. Do not trust values in hidden fields.
  • Permissions: Re-validate user roles and permissions on the server for every sensitive action. Use framework decorators (@permission_required, @user_passes_test, DRF permission_classes = [IsAdminUser]).

Secure Code Example

# views/checkout.py (Secure)
@login_required
def process_checkout_secure(request):
    if request.method == 'POST':
        item_id = request.POST.get('item_id')
        # SECURE: Retrieve product and its price from the database server-side.
        try:
            product = Product.objects.get(pk=item_id)
            price = product.price # Use authoritative price from DB
        except Product.DoesNotExist:
            return HttpResponse("Invalid item", status=400)

        # ... charge user based on the server-retrieved 'price' ...
        charge_user(request.user, price) # Charges correct amount
        return HttpResponse("Charged!")
    # ...

# api/admin_views.py (Secure)
from rest_framework.permissions import IsAdminUser # Use correct permission

@api_view(['POST'])
@permission_classes([IsAdminUser]) # SECURE: Checks if user is admin on server.
def delete_all_users_secure(request):
    User.objects.all().delete()
    return Response(status=204)

Testing Strategy

Use browser developer tools or an intercepting proxy (like Burp Suite) to modify client-side data before it’s submitted: change values in hidden fields (prices, user IDs, roles), re-enable disabled buttons, remove readonly attributes, modify JavaScript variables influencing submission. Check if the server accepts and processes the manipulated data or if it correctly rejects/ignores it based on server-side validation and state.