CWE-282: Improper Ownership Management
Overview
Improper ownership management happens when a file, directory, process, or other resource is created, transferred, or left with an owner other than the specific principal the application intended - world-writable output, a service's data files left root-owned, an installer that doesn't pin the owner it installs as. Once ownership is wrong, whoever holds it can read or modify the resource directly, bypassing whatever access control the application layers on top.
Relationship to Other CWEs
- CWE-282 (this page) - ownership of a resource that is assigned, checked or changed incorrectly
- CWE-284 (Improper Access Control) - the parent
- CWE-283 (Unverified Ownership) - a child: acting on a resource without first confirming who currently owns it. No page here
- CWE-708 (Incorrect Ownership Assignment) - the other child, and the case this page's examples mostly illustrate: setting the wrong owner at creation time, or letting an ownership change follow a symlink to the wrong target. For a finding specifically about a race condition, a symlink-following ownership change, or ownership left behind after a session ends, that page covers the ground in more depth
- CWE-732 (Incorrect Permission Assignment for Critical Resource) - closely related in practice but not a sibling. It sits a level deeper, under CWE-285 (Improper Authorization) - itself a child of CWE-284 - and under CWE-668 (Exposure of Resource to Wrong Sphere). MITRE records no direct relationship between CWE-732 and this page. The two routinely appear on the same finding, wrong owner and wrong permission bits on the same resource, so treat ownership and permissions as two separate checks even when fixing them in the same change
- CWE-272, CWE-273 and CWE-274 - despite the shared privilege and access theme, MITRE records no formal relationship between these and CWE-282. They cover privilege level, not resource ownership
OWASP Classification
A01:2025 - Broken Access Control
Risk
High: incorrect ownership lets an unintended principal read or modify a resource directly. A config file owned by the wrong service account leaks credentials to any process running as that account; a root-owned file in a shared upload directory can't be managed by the service that's supposed to own it; a world-writable file lets any local user tamper with it, a common path to privilege escalation and persistence once an attacker has any foothold on the host.
Remediation Steps
Core Principle: Create the resource as the principal that should own it - never rely on an ambient default - and where ownership has to be set after the fact, set it on an open descriptor and read it back rather than assuming the call took effect.
Trace the Operation
- Source: Code or deployment tooling that creates a file, directory, or other resource (an application writing output, an installer, a provisioning script).
- Sink: The point where another process or user reads or writes that resource, trusting that ownership restricts who can do so.
- Missing control: No explicit ownership assignment at creation (the resource inherits whatever the creating process's identity happens to be), or ownership set but never verified.
Create the Resource as the Intended Owner (Primary Defense)
Changing an owner after the fact is a privileged operation: on Linux, only a privileged process - one with the CAP_CHOWN capability - may change the owner of a file, so a service running as an ordinary account gets EPERM whenever it tries to change the owner, and a fail-closed check on the result then stops the service outright. The same page notes the one thing an unprivileged owner can do: change the file's group to any group it is itself a member of. Create the resource as the identity that should own it instead of correcting the ownership afterwards. For a service that is never privileged, that identity is set outside the code - User= in a systemd unit, sudo -u, a container USER directive - and the code's part is the umask and the mode:
// VULNERABLE - pseudo-code
create_file(path, contents)
// Attack: the file inherits whatever identity the creating process happens
// to run as, and whatever mode the ambient umask allows - often a deployment
// or build account rather than the service that has to read or write it
// Result: the wrong local principal can read or modify the resource directly
// SECURE - pseudo-code, process already running as the intended identity
set_umask(restrictive_mask) // no group or world bits by default
create_file(path, contents) // correct owner at creation, no chown needed
// SECURE - pseudo-code, privileged launcher that drops first
drop_to(intended_user, intended_group) // requires CAP_SETUID/CAP_SETGID; see CWE-273
set_umask(restrictive_mask)
create_file(path, contents) // created as the service identity, not as root
A setgid directory does the same job for the group: files created inside it take the directory's group rather than the creator's primary group, so a shared data directory stays consistent without a per-file call.
Where a Privileged Process Sets the Owner
An installer or provisioning script that genuinely runs with CAP_CHOWN can set ownership directly - but on the open descriptor, not the path:
// SECURE - pseudo-code, privileged creator
fd = create_file_exclusively(path, minimum_required_mode)
set_owner_on_descriptor(fd, intended_user, intended_group) // fchown, not chown
set_permissions_on_descriptor(fd, minimum_required)
owner = get_owner_of_descriptor(fd)
if owner != (intended_user, intended_group):
fail_closed("ownership assignment did not take effect")
The pseudo-code above depends on three facts about ownership changes, all documented in chown(2):
- Path-based
chowndereferences symlinks. Achownon a path inside a directory an attacker can write to can be redirected onto a file they don't own - the classic ownership race, covered in more depth on CWE-708.fchownon a descriptor you opened yourself doesn't re-resolve the path.lchowndoesn't follow the link either, but it changes the symlink's ownership rather than the target's, so it is a way to refuse being redirected, not a substitute for the intended operation. Where only a path is available, open it withO_NOFOLLOWand usefchownon the result. - Changing owner or group clears the setuid and setgid bits on an executable. Linux applies this to root as well as to unprivileged callers, so a chown run after the mode was set silently un-does a setuid binary. Set the mode after the ownership, not before.
- A call that didn't raise an error isn't proof the change took effect. Some platforms report success on a partial change. Reading the ownership back and comparing it to what was intended catches that case.
Match Ownership to the Principle of Least Privilege
- Executables should be owned by an administrative account (root) and not writable by the service account that runs them - a service account able to modify its own binary can escalate to whatever that binary runs as.
- Application data, working directories, and log files should be owned by the dedicated service account, not root, so the running service can manage its own files without broader access.
- Never use world-writable permissions as a shortcut for cross-process access - grant the specific group that needs write access instead.
Typical ownership and permissions by resource type:
| Resource | Owner | Permissions | Why |
|---|---|---|---|
| Executable | root (or build/deploy account) | not group/world-writable | Service account shouldn't be able to modify its own binary |
| Sensitive config (credentials) | service account | owner read/write only | No other local account or process should be able to read it |
| Application data directory | service account | owner + group only | Service manages its own data; no world access |
| Log file | service account (or a log-management group) | owner write, group read | Log tooling can read without being able to tamper |
Deploy Ownership Consistently
Set ownership as an explicit, scripted step in deployment, not a manual follow-up, and re-verify it as part of routine auditing. Ownership drifts silently after a manual fix, a redeploy that recreates a file as a different user, or a package upgrade that resets permissions to its own defaults.
Test with an Unprivileged Session
- Confirm the intended service account can read and write the resources it needs, without needing broader permissions.
- Run the ownership step as the identity that performs it in production, not as root in a test shell. A
chownthat only worked because the tester was privileged fails withEPERMon the real deploy. - Confirm the service account cannot write to executables or other resources it doesn't own.
- Scan for world-writable files and files owned by an unexpected principal (particularly root, in directories a non-root service manages) as part of routine auditing.
- Re-scan with the security scanner to confirm the finding is resolved.
Common Vulnerable Patterns
- Creating a file or directory as whatever identity the tooling happens to run as, with no step that pins the owner to the intended principal
- Using world-writable permissions instead of granting the specific account or group that needs access. This is the permission half of the finding, CWE-732 rather than ownership, but it lands on the same resource often enough that the owner is worth checking whenever it appears
- Sensitive files (credentials, private keys, session data) owned by or readable by an account other than the one that needs them
- Executables owned by the same service account that runs them, allowing self-modification
- Deployment scripts that install files without a corresponding ownership step
Common Pitfalls
- Setting permissions but not ownership: a deployment script restricts a config file's mode bits but never changes the owner. Restrictive permissions only protect the file from other accounts if the owner is also correct; a root-owned file with owner-only permissions still isn't readable by the service account that's supposed to use it.
- Assuming an archive or package manager leaves correct ownership: it applies the ownership the archive or package carries, not the one you chose. GNU tar run as root restores the ownership recorded in the archive (
--same-owneris the default behavior for the superuser). It resolves the stored user and group names against the local account database, falls back to the numeric ids when a name is absent or does not resolve, or uses the ids outright under--numeric-owner; the owner is whichever local account happens to match. Run by an ordinary user,--no-same-owneris the default and everything becomes the extracting user's. rpm and dpkg record per-file owner and group in their package metadata and apply it at install time. In each case the owner afterwards is whatever the artifact says, so set it explicitly after install, a step that is easy to skip on redeploys. - Fixing ownership once, manually, instead of scripting it: a one-time ownership fix to resolve a finding gets undone the next time the directory is recreated, the service is redeployed, or a package is upgraded. Ownership needs to be part of the repeatable deployment process, not a step performed once by hand.