Skip to content

CWE-272: Least Privilege Violation

Overview

Least privilege violation occurs when processes, users, or code run with more permissions than required (running as root, admin, SYSTEM, DBA), enabling greater damage from vulnerabilities, privilege escalation, and lateral movement. Services should run with minimal permissions needed for their function.

Risk

High: Excessive privileges amplify impact of vulnerabilities, enable privilege escalation, allow attackers to compromise entire system (not just application), bypass security controls, persist via system-level access, and violate compliance requirements (PCI-DSS, SOC2).

Remediation Steps

Core principle: Apply least privilege everywhere: run with minimal rights and grant only the permissions required for the task.

See CWE-273 for detailed privilege dropping and verification guidance.

Identify Excessive Privileges

When reviewing security scan results:

  • Check process owner: Verify services not running as root/Administrator/SYSTEM
  • Review database permissions: Check for DBA/owner privileges on app connections
  • Check file system access: Identify write access to system directories
  • Review container configs: Look for USER root in Dockerfiles
  • Check systemd/service configs: Find services without User= directive

Common violations:

# Running web server as root
$ ps aux | grep nginx
root  1234  nginx  # WRONG - should be www-data or nginx user

# Container running as root
USER root  # In Dockerfile - WRONG!

# Database connection as admin
spring.datasource.username=postgres  # WRONG - should be app-specific user

Run with Minimal Permissions (Primary Defense)

Linux service configuration:

# /etc/systemd/system/myapp.service

[Service]
# VULNERABLE - runs as root by default
ExecStart=/usr/bin/myapp

# SECURE - run as dedicated user
[Service]
Type=simple
User=myapp
Group=myapp
ExecStart=/usr/bin/myapp

# Additional restrictions
NoNewPrivileges=true
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadOnlyPaths=/
ReadWritePaths=/var/lib/myapp /var/log/myapp

# Restrict capabilities
CapabilityBoundingSet=
AmbientCapabilities=

# Restrict system calls
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

Create dedicated service account:

# Create system user (no login shell)
sudo useradd -r -s /bin/false -d /var/lib/myapp -c "MyApp Service" myapp

# Create necessary directories
sudo mkdir -p /var/lib/myapp /var/log/myapp
sudo chown myapp:myapp /var/lib/myapp /var/log/myapp
sudo chmod 750 /var/lib/myapp /var/log/myapp

# Ensure app binary is not writable
sudo chown root:root /usr/bin/myapp
sudo chmod 755 /usr/bin/myapp

Docker - run as non-root:

# VULNERABLE - runs as root
FROM node:18
COPY . /app
WORKDIR /app
CMD ["node", "server.js"]  # Runs as root!

# SECURE - create and use non-root user
FROM node:18

# Create app user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Set up app directory
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .

# Change ownership to app user
RUN chown -R appuser:appuser /app

# Switch to non-root user
USER appuser

# Run as non-root
EXPOSE 8080
CMD ["node", "server.js"]

Use Database Accounts with Minimal Grants

-- VULNERABLE - app uses DBA account
-- spring.datasource.username=postgres
-- spring.datasource.password=admin123

-- SECURE - create app-specific user with minimal permissions

-- PostgreSQL

CREATE USER myapp_user WITH PASSWORD 'strong_password_here';

-- Grant only necessary permissions

GRANT CONNECT ON DATABASE myapp_db TO myapp_user;
GRANT USAGE ON SCHEMA public TO myapp_user;

-- Grant table-specific permissions

GRANT SELECT, INSERT, UPDATE, DELETE ON users TO myapp_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON posts TO myapp_user;

-- NO admin privileges:
-- NO CREATE DATABASE
-- NO DROP TABLE
-- NO ALTER SCHEMA
-- NO GRANT OPTION

-- Verify permissions

SELECT grantee, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE grantee = 'myapp_user';
-- MySQL

CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'strong_password';

-- Grant minimal permissions

GRANT SELECT, INSERT, UPDATE, DELETE 
ON myapp_db.* 
TO 'myapp_user'@'localhost';

-- NO GRANT OPTION
-- NO DROP
-- NO CREATE USER
-- NO SUPER privilege

FLUSH PRIVILEGES;

-- Verify

SHOW GRANTS FOR 'myapp_user'@'localhost';

Drop Privileges After Initialization

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>

// VULNERABLE - continues running as root
void start_server_bad() {
    // Bind to privileged port (requires root)
    int sock = bind_to_port(80);

    // BUG: Still running as root!
    handle_requests(sock);
}

// SECURE - drop privileges after binding
void start_server_safe() {
    // Must start as root to bind to port 80
    if (getuid() != 0) {
        fprintf(stderr, "Must run as root to bind to port 80\n");
        exit(1);
    }

    // Bind to privileged port while root
    int sock = bind_to_port(80);

    if (sock < 0) {
        perror("Failed to bind");
        exit(1);
    }

    // NOW drop privileges permanently
    struct passwd *pw = getpwnam("nobody");
    if (pw == NULL) {
        fprintf(stderr, "User 'nobody' not found\n");
        exit(1);
    }

    // Drop supplementary groups
    if (setgroups(0, NULL) != 0) {
        perror("setgroups failed");
        exit(1);
    }

    // Set GID
    if (setgid(pw->pw_gid) != 0) {
        perror("setgid failed");
        exit(1);
    }

    // Set UID (do this last!)
    if (setuid(pw->pw_uid) != 0) {
        perror("setuid failed");
        exit(1);
    }

    // VERIFY we're not root anymore
    if (getuid() == 0 || geteuid() == 0) {
        fprintf(stderr, "Failed to drop root privileges!\n");
        exit(1);
    }

    printf("Dropped privileges to UID %d\n", getuid());

    // Handle requests as unprivileged user
    handle_requests(sock);
}

Use Linux Capabilities Instead of Root

#include <sys/capability.h>
#include <sys/prctl.h>

// Instead of running as root, use specific capability
void bind_with_capability() {
    // Grant CAP_NET_BIND_SERVICE capability
    // This allows binding to ports < 1024 without being root

    cap_t caps = cap_init();
    cap_value_t cap_list[1] = { CAP_NET_BIND_SERVICE };

    if (cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET) == -1) {
        perror("cap_set_flag");
        exit(1);
    }

    if (cap_set_proc(caps) == -1) {
        perror("cap_set_proc");
        exit(1);
    }

    cap_free(caps);

    // Now can bind to port 80 without being root!
    int sock = bind_to_port(80);

    // Drop the capability after binding
    cap_t empty_caps = cap_init();
    cap_set_proc(empty_caps);
    cap_free(empty_caps);
}

Set capability on binary:

# Instead of setuid root, grant specific capability
sudo setcap cap_net_bind_service=+ep /usr/bin/myapp

# Run as regular user
./myapp  # Can bind to port 80 without being root

# Verify capabilities
getcap /usr/bin/myapp

Test Privilege Configuration

#!/bin/bash

echo "Testing least privilege configuration..."

# Test 1: Service not running as root
if ps aux | grep myapp | grep -q "^root"; then
    echo "✗ Service running as root!"
    exit 1
else
    echo "✓ Service not running as root"
fi

# Test 2: Service running as dedicated user
if ps aux | grep myapp | grep -q "^myapp"; then
    echo "✓ Service running as dedicated user"
else
    echo "✗ Service not running as dedicated user"
    exit 1
fi

# Test 3: Service cannot write to system directories
sudo -u myapp touch /etc/test 2>/dev/null
if [ $? -eq 0 ]; then
    echo "✗ Service can write to /etc!"
    sudo rm /etc/test
    exit 1
else
    echo "✓ Service cannot write to system directories"
fi

# Test 4: Database user has minimal permissions
psql -U myapp_user -d myapp_db -c "DROP TABLE users" 2>&1 | grep -q "permission denied"
if [ $? -eq 0 ]; then
    echo "✓ Database user cannot DROP tables"
else
    echo "✗ Database user has excessive permissions!"
    exit 1
fi

# Test 5: Container running as non-root
docker inspect myapp | jq -r '.[0].Config.User' | grep -qv "^$\|^0$\|^root$"
if [ $? -eq 0 ]; then
    echo "✓ Container running as non-root user"
else
    echo "✗ Container running as root!"
    exit 1
fi

echo "All privilege tests passed!"

Common Vulnerable Patterns

  • Running web servers as root
  • Database connections as db owner/admin
  • Services running as SYSTEM/Administrator
  • Containers running as root
  • Sudo without restrictions

Security Checklist

  • Services run as dedicated non-root users
  • Database connections use app-specific accounts (not DBA)
  • Containers run as non-root (USER directive in Dockerfile)
  • systemd services have User= and Group= directives
  • File permissions restrict write access to app directories only
  • Capabilities used instead of setuid root where possible
  • Privileges dropped after initialization if needed
  • Tests verify services not running as root

Additional Resources