Skip to content

CWE-732: Incorrect Permission Assignment for Critical Resource

Overview

A file, directory, or service is created with permissions broader than it needs, so accounts that should have no business with the resource can read or modify it.

Relationship to Other CWEs

If the defect is that nothing checked a permission, the finding is CWE-862 (Missing Authorization) or CWE-863 (Incorrect Authorization), not this page. MITRE marks CWE-732 ALLOWED-WITH-REVIEW for exactly that reason - it is "often misused for vulnerabilities in which 'permissions' are not checked". This page is for a permission that was assigned too broadly, whether or not anything later consults it. Its parents are CWE-285 (Improper Authorization) and CWE-668 (Exposure of Resource to Wrong Sphere).

The pages around it differ by what the wrong permission is attached to:

  • CWE-732 (this page) - the mode or ACL on a specific named resource grants more than it needs
  • CWE-276 (Incorrect Default Permissions) - the wrong permission is the one new resources are created with rather than one assigned to a named resource afterwards. Fix it by changing the default; fix CWE-732 by fixing the assignment
  • CWE-560 (Use of umask() with chmod-style Argument) - the concrete mistake that most often produces that bad default: umask() names the bits to withhold, so a chmod-style argument grants exactly what it looks like it denies
  • CWE-282 (Improper Ownership Management) - the owner is wrong rather than the mode. The two land on the same resource often enough that ownership and permissions are worth checking as two separate questions
  • CWE-269 (Improper Privilege Management) - the privilege of the process doing the accessing, rather than the permissions of the resource being accessed

MITRE gives CWE-732 six further children with no page here: CWE-277, CWE-278, CWE-279 and CWE-281 cover inherited, preserved and execution-assigned permissions, which the guidance below treats the same way as any other over-broad assignment; CWE-766 is a data element declared public; and CWE-1004 is a session cookie without HttpOnly, which belongs to the cookie-attribute family CWE-614 covers rather than to anything on this page.

OWASP Classification

A01:2025 - Broken Access Control

Risk

High: Any account holding the granted access can read, modify, or delete the resource. For a credentials file that means disclosure; for a config file or an executable it usually means privilege escalation.

Remediation Steps

Core Principle: Apply least privilege to every permission and ACL: default-deny, and grant read, write, or execute only where an operation needs it.

Locate the incorrect permission assignment

  • Identify the file, directory, or resource the finding names
  • Work out who legitimately needs access and which operations they need
  • Read the current permissions with ls -l on Unix or Get-Acl on Windows
  • Establish what the resource holds - config, database, logs, a private key, user data - because the correct mode follows from that

Apply least privilege to all resources (Primary Defense)

  • Grant only the permissions the legitimate operations need
  • Remove write and execute unless something needs them. A config file needs owner access only: 400 if nothing rewrites it at runtime, 600 if something does, and never the execute bit
  • Set the mode explicitly. These are POSIX modes; Windows is covered below
    • 400: Owner read-only (sensitive config, private keys)
    • 600: Owner read/write (database files, user data)
    • 640: Owner read/write, service group read (config read by one daemon)
    • 644: Owner read/write, group/world read (deliberately public content)
    • 755: Owner all, group/world read/execute (scripts and binaries that everyone runs)
  • Avoid world-writable modes: 777 and 666 let any local account rewrite the resource, and nothing legitimate needs them
  • Never give a secret a world-readable mode. 644 and 755 both grant read to every account on the host, so they are wrong for a private key or a credentials file and right for a binary or a public asset. The mode is only correct relative to what the file holds

Shared directories are the exception to "never 777". A directory that several users write to needs the sticky bit as well - mode 1777, which is what /tmp carries - so that a user can delete only their own entries. Without it, any local account can remove or replace another user's file in that directory, which is how a correctly-permissioned file gets swapped out from underneath its owner. See CWE-377 for the temporary-file case.

On Windows there are no numeric modes. Access is an ACL of individual entries, chmod has no equivalent, and Python's os.chmod toggles only the read-only attribute there. Set the DACL explicitly - icacls file /inheritance:r /grant:r "%USERNAME%:(R)", or Set-Acl with a FileSecurity object built from scratch - and breaking inheritance is the step that matters: a file created in a directory that grants Users read inherits that grant no matter what else is added to it.

Use secure defaults and explicit permissions

  • Set umask 027 or umask 077 so new files start restrictive. In code rather than in a shell, note that umask() takes the bits to withhold - a chmod-style 0600 passed to it grants what it appears to deny, which is CWE-560
  • Set permissions explicitly in code with chmod, os.chmod(), or File.setReadable()
  • Do not rely on system defaults, which are commonly 644 or 755. A resource left at whatever the default produced is CWE-276 rather than this page, and the fix is the default rather than the individual resource
  • Set the mode before the sensitive data is written, not after - the secure pattern below covers why the order matters

Apply additional permission controls

  • Set the owning user and group with chown or the platform equivalent
  • Use filesystem ACLs where the owner/group/other model is too coarse
  • Encrypt keys, credentials, and PII at rest as well as restricting the mode
  • Create temporary files with tempfile.NamedTemporaryFile() or Files.createTempFile(), which create the file exclusively and, on POSIX, at mode 0600. On Windows the protection comes from %TEMP% sitting inside the user's profile rather than from the call, so it is lost the moment a path is hardcoded to something like C:\Temp - see CWE-377

Regularly review and audit permissions

  • Sweep for unnecessary access periodically; find / -perm -002 lists what is world-writable
  • Run a configuration scanner such as Lynis or OpenSCAP, or the equivalent cloud posture tool
  • Audit-log access to and modification of sensitive resources
  • Alert on failed permission checks and on privilege escalation attempts

Test the permission changes

  • Confirm the corrected mode with ls -l, Get-Acl, or stat()
  • Try the resource as a different user and a different group, and confirm the access is refused
  • Confirm the application still works: it can read its config and write its logs
  • Cover file creation, log rotation, and backup and restore, where the mode is easily lost
  • Re-scan to confirm the finding is resolved

Common Vulnerable Patterns

  • Granting world-writable or world-readable permissions
  • Using default or inherited permissions without review

World-Writable Permissions on Critical File (Bash)

# VULNERABLE - World-Writable Permissions on Critical File
# Creates file with world-writable permissions
chmod 777 /etc/critical.conf

Why this is vulnerable: Mode 777 lets any local account rewrite the file, and a configuration file is instructions - so whoever can write it decides what the service does on its next restart: where it connects, which certificate it trusts, whether a check runs at all. That is privilege escalation by way of a text editor, with no exploit involved.

Two things make it worse than the mode alone suggests. The execute bit has no bearing on a config file and is granted here anyway, which is a sign the value was chosen to make an error go away rather than to describe an intent; and if the containing directory is also world-writable, the file can be replaced rather than edited, so restoring its contents does not restore its ownership. Set the narrowest mode that works - 640 with a service group is the usual answer for a config file read by one daemon - and check the directory as well as the file.

Secure Patterns

Owner-Only Read/Write Permissions (Bash)

# Tightening a file that already exists
chmod 600 /etc/critical.conf   # owner read/write
chmod 400 /etc/critical.conf   # owner read-only

# Creating one: set the mode before the content goes in, not after. chmod only
# changes a file that already exists, so writing first and chmod-ing second
# leaves the contents readable in between
install -m 600 /dev/null /etc/critical.conf   # empty file, correct mode
printf '%s\n' "$secret" > /etc/critical.conf

# Same idea with umask, where the redirect itself does the creating
( umask 077; printf '%s\n' "$secret" > /etc/critical.conf )

Why this works:

  • 600 gives the owner read and write, 400 read alone, and group and other nothing in either case. No other local account can read the secret, or edit the file to change what the service does on its next start
  • Order matters, and chmod alone does not fix a creation path. A script that writes a key and then calls chmod 600 created that file under the process umask - commonly 022, so world-readable - and the secret was on disk at that mode until the chmod returned. The window is short, and short is not the same as closed: another local process only has to open the file once, and it keeps the descriptor at the old access afterwards. Where the code creates the file, set the mode at creation (install -m, umask, or the platform's create-with-mode call) and treat a following chmod as a repair for files that already exist.

Additional Resources