Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
SELinux and AppArmor are Linux’s two mainstream Mandatory Access Control (MAC) systems — a layer enforced *in addition to* standard discretionary permissions (owner/group/mode bits) that the kernel checks on every relevant operation, regardless of what the file’s owner allows. Where traditional Unix permissions ask “is this user allowed to touch this file,” MAC asks “is this specific *process, in this specific security context*, allowed to do this specific thing to this specific resource” — and it applies even to root, which is what makes it valuable as a last line of defense against a compromised or malicious process.
The two systems take different philosophical approaches — SELinux labels every subject and object and enforces policy centrally by type, AppArmor attaches a profile per binary path and is comparatively easier to author — but both fail the same way in practice: a policy or profile that is too permissive, or a system left in a non-enforcing mode, provides zero protection while giving administrators false confidence that MAC is “on.” Auditors and attackers alike check enforcement status first, because a MAC system in permissive/complain mode is functionally identical to having none at all — except for the log noise.
Attack Prerequisites
Bypassing or nullifying SELinux/AppArmor protections typically requires one of:
- Root or
CAP_SYS_ADMIN/CAP_MAC_ADMIN, needed to flip enforcement mode (setenforce 0,aa-complain) or load new/broader policy modules. - A process already running unconfined — labeled
unconfined_tunder SELinux, or with no AppArmor profile loaded/attached — so that any code execution inside it inherits no MAC restriction at all. - An overly broad policy generated by
audit2allowapplied without review, effectively whitelisting whatever a service happened to do during a noisy test run. - A profile shipped in
complain/permissive mode by a distribution package and never switched to enforce.
How It Works
SELinux enforces type enforcement: every process runs with a security *domain* (e.g. httpd_t) and every object — file, socket, device — carries a *type* (e.g. httpd_sys_content_t). The policy is a set of allow rules of the form “domain X may perform action Y on type Z,” and by default everything not explicitly allowed is denied — a default-deny, whitelist model. Labels are stored as extended attributes (security.selinux) and must be kept correct as files move or are created; a mislabeled file (wrong type) is either wrongly denied or, more dangerously, wrongly granted access it should not have.
AppArmor instead attaches a profile per executable path, listing the files, capabilities, and network operations that binary may use, and supports both enforce mode (violations blocked and logged) and complain mode (violations only logged, nothing blocked — commonly used during profile development and, mistakenly, left in production). Because AppArmor is path-based rather than label-based, it is generally easier to author and reason about, but it also means a process that starts under a different, unprofiled path (e.g. exec’d from an unexpected location, or via bash -c reinterpreting the binary) may run entirely unconfined even though a profile exists for the “normal” path.
Both systems share a critical property: they are *policy engines*, not automatic protections. A process the policy grants broad allow rules to — whether from a vendor’s overly generous default policy or from an administrator running audit2allow -a -M mymodule and loading whatever it produced without review — is just as exposed as if MAC were absent, because the kernel is faithfully enforcing exactly what it was told to.
Vulnerable Code / Configuration
The most common enabling misconfiguration is enforcement simply turned off, or a custom policy module generated by blindly running audit2allow against denial logs and loading the result without reading it:
$ getenforce
Permissive # VULNERABLE: logs denials, blocks nothing
$ ausearch -m avc -ts recent | audit2allow -M httpd_broad
$ semodule -i httpd_broad.pp # loaded without reviewing the rules
$ semodule -l | grep httpd_broad
httpd_broad
Bash# Resulting module, if inspected, often contains sweeping grants like:
allow httpd_t unconfined_t:process { transition sigchld };
allow httpd_t admin_home_t:file { read write open getattr };
# The web server domain can now read/write files under user home
# directories it was never intended to touch.
TEXTThe AppArmor equivalent is a profile shipped or edited into complain mode and forgotten:
$ aa-status
apparmor module is loaded.
3 profiles are in enforce mode.
1 profiles are in complain mode.
/usr/sbin/nginx # VULNERABLE: logs only, does not block
BashWalkthrough / Exploitation
An attacker who already has a foothold in a confined service checks enforcement status first — it determines whether MAC is a real obstacle:
getenforce # SELinux: Enforcing / Permissive / Disabled
id -Z # current SELinux context
aa-status # AppArmor: per-binary enforce/complain state
BashIf the target domain/profile is permissive or complain, or if the current process is unconfined_t, MAC is not a factor — proceed with normal post-exploitation. If enforcing but over-permissive (the audit2allow-generated module above), the exploit path is simply to use the access the sloppy module already granted:
# Web-shell context, httpd_t domain, with the broad module loaded above
cat /home/appadmin/.ssh/id_rsa # allowed by admin_home_t grant
ls -la /root/ # test other newly-granted paths
BashWhere the attacker has root but SELinux/AppArmor is actively blocking a specific action, disabling enforcement (loudly, but effectively) removes the obstacle outright:
setenforce 0 # SELinux -> permissive, root required
aa-complain /usr/sbin/nginx # AppArmor -> complain for one profile
systemctl stop apparmor # or disable the whole subsystem
BashNote:
setenforce 0only survives until reboot or the nextsetenforce 1; persistent disablement requires editing/etc/selinux/config(SELINUX=disabled) or/etc/selinux/configset to permissive, and for AppArmor, removing the profile from/etc/apparmor.d/or disabling theapparmorservice/kernel parameter — both are far more visible changes than a transient runtime flip.
Opsec: Every AVC denial (even a permissive-mode one that was not enforced) is logged to the audit subsystem with
type=AVC, andsetenforce/aa-complaincalls generate their own audit records — a SIEM rule ontype=MAC_STATUSor on enforcement-mode transitions catches this class of tampering even when the follow-on activity looks benign.
Detection and Defense
MAC systems are only protective when enforcing and correctly scoped:
- Verify
getenforce/aa-statusreturn Enforcing across the fleet, not just installed-but-permissive; treat Permissive/Disabled as a finding, not a baseline state. - Never load
audit2allowoutput without reading it — trim generated modules to the minimum rules needed and prefer targeted booleans (semanage boolean) over broad custom modules. - Alert on
type=AVCdenials and ontype=MAC_STATUS/setenforceevents viaausearch -m avc,mac_status, and on AppArmor’sapparmor="STATUS"kernel log lines. - Keep file labels correct —
restorecon -Rafter out-of-band file operations, and audit forunconfined_t/unlabeled processes running network-facing services. - Pin custom policy/profile changes behind change control and review diffs the same way you would review a firewall rule change.
Real-World Impact
MAC bypass and misconfiguration is a routine finding in Linux hardening assessments and CIS Benchmark audits, and “disable SELinux” remains a widely (and dangerously) repeated troubleshooting step in forum answers and outdated internal runbooks, which is exactly how production systems end up permanently in SELINUX=disabled. Container platforms including OpenShift rely heavily on correctly enforced SELinux to contain container-to-host escapes, making silent permissive-mode drift a meaningful risk in those environments specifically.
Conclusion
SELinux and AppArmor add a default-deny, kernel-enforced layer that survives even a root compromise of discretionary permissions — but only when actually enforcing and only as strict as the policy loaded. Verify enforcement status as a first-class fleet metric, treat every audit2allow-generated module as a draft to be trimmed rather than shipped, and alert on enforcement-mode transitions so a setenforce 0 or aa-complain becomes a detected event rather than a silent hole.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments