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 rootin 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