selinux.md (15469B)
1 --- 2 title: "SELinux" 3 section: "Linux" 4 sectionSlug: "linux-hardening" 5 sourcePath: "src/linux-hardening/interesting-files-permissions/selinux.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/linux-hardening/interesting-files-permissions/selinux.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # SELinux 14 15 SELinux is a **label-based Mandatory Access Control (MAC)** system. In practice, this means that even if DAC permissions, groups, or Linux capabilities look enough for an action, the kernel can still deny it because the **source context** is not allowed to access the **target context** with the requested class/permission.<sup>[[1]](#references)</sup> 16 17 A context usually looks like:<sup>[[1]](#references)</sup> 18 19 ```text 20 user:role:type:level 21 system_u:system_r:httpd_t:s0 22 unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 23 ``` 24 25 From a privesc perspective, the `type` (domain for processes, type for objects) is usually the most important field:<sup>[[1]](#references)</sup> 26 27 - A process runs in a **domain** such as `unconfined_t`, `staff_t`, `httpd_t`, `container_t`, `sysadm_t` 28 - Files and sockets have a **type** such as `admin_home_t`, `shadow_t`, `httpd_sys_rw_content_t`, `container_file_t` 29 - Policy decides whether one domain can read/write/execute/transition to the other 30 31 ## Fast Enumeration 32 33 If SELinux is enabled, enumerate it early because it can explain why common Linux privesc paths fail or why a privileged wrapper around a "harmless" SELinux tool is actually critical:<sup>[[1]](#references)</sup> 34 35 ```bash 36 getenforce 37 sestatus 38 id -Z 39 ps -eZ | head 40 cat /proc/self/attr/current 41 ls -Zd / /root /home /tmp /etc /var/www 2>/dev/null 42 ``` 43 44 Useful follow-up checks:<sup>[[1]](#references)[[3]](#references)[[4]](#references)[[7]](#references)[[12]](#references)</sup> 45 46 ```bash 47 # Installed policy modules and local customizations 48 semodule -lfull 2>/dev/null 49 semanage fcontext -C -l 2>/dev/null 50 semanage permissive -l 2>/dev/null 51 semanage login -l 2>/dev/null 52 semanage user -l 2>/dev/null 53 54 # Labels that frequently reveal mistakes or unusual paths 55 find / -context '*:default_t:*' -o -context '*:file_t:*' 2>/dev/null 56 57 # Compare current label vs policy default for a path 58 matchpathcon -V /path/of/interest 2>/dev/null 59 restorecon -n -v /path/of/interest 2>/dev/null 60 ``` 61 62 Interesting findings:<sup>[[1]](#references)[[3]](#references)[[7]](#references)[[19]](#references)</sup> 63 64 - `Disabled` or `Permissive` mode removes most of the value of SELinux as a boundary. 65 - `unconfined_t` usually means SELinux is present but not meaningfully constraining that process. 66 - `default_t`, `file_t`, or obviously wrong labels on custom paths often indicate mislabeling or incomplete deployment. 67 - Local overrides in `file_contexts.local` take precedence over policy defaults, so review them carefully. 68 69 ## Policy Analysis 70 71 SELinux is much easier to attack or bypass when you can answer two questions: 72 73 1. **What can my current domain access?** 74 2. **What domains can I transition into?** 75 76 The most useful tools for this are `sepolicy` and **SETools** (`seinfo`, `sesearch`, `sedta`):<sup>[[2]](#references)[[9]](#references)</sup> 77 78 ```bash 79 # Transition graph from the current domain 80 sepolicy transition -s "$(id -Z | awk -F: '{print $3}')" 2>/dev/null 81 82 # Search allow and type_transition rules 83 sesearch -A -s staff_t 2>/dev/null | head 84 sesearch --type_transition -s staff_t 2>/dev/null | head 85 86 # Inspect policy components 87 seinfo -t 2>/dev/null | head 88 seinfo -r 2>/dev/null | head 89 ``` 90 91 This is especially useful when a host uses **confined users** rather than mapping everyone to `unconfined_u`. In that case, look for:<sup>[[3]](#references)</sup> 92 93 - user mappings via `semanage login -l` 94 - allowed roles via `semanage user -l` 95 - reachable admin domains such as `sysadm_t`, `secadm_t`, `webadm_t` 96 - `sudoers` entries using `ROLE=` or `TYPE=` 97 98 If `sudo -l` contains entries like this, SELinux is part of the privilege boundary:<sup>[[3]](#references)</sup> 99 100 ```text 101 linux_user ALL=(ALL) ROLE=webadm_r TYPE=webadm_t /bin/bash 102 ``` 103 104 Also check whether `newrole` is available:<sup>[[3]](#references)[[10]](#references)[[11]](#references)</sup> 105 106 ```bash 107 sudo -l 108 which newrole runcon 109 newrole -l 2>/dev/null 110 ``` 111 112 `runcon` and `newrole` are not automatically exploitable, but if a privileged wrapper or a `sudoers` rule lets you select a better role/type, they become high-value escalation primitives.<sup>[[3]](#references)[[10]](#references)[[11]](#references)</sup> 113 114 ## Files, Relabeling, and High-Value Misconfigurations 115 116 The most important operational difference between common SELinux tools is:<sup>[[1]](#references)[[6]](#references)[[7]](#references)[[8]](#references)</sup> 117 118 - `chcon`: temporary label change on a specific path 119 - `semanage fcontext`: persistent path-to-label rule 120 - `restorecon` / `setfiles`: apply the policy/default label again 121 122 This matters a lot during privesc because **relabeling is not just cosmetic**. It can turn a file from "blocked by policy" into "readable/executable by a privileged confined service".<sup>[[1]](#references)[[7]](#references)[[8]](#references)</sup> 123 124 Check for local relabel rules and relabel drift:<sup>[[1]](#references)[[7]](#references)[[8]](#references)</sup> 125 126 ```bash 127 grep -R . /etc/selinux/*/contexts/files/file_contexts.local 2>/dev/null 128 restorecon -nvr / 2>/dev/null | head -n 50 129 matchpathcon -V /etc/passwd /etc/shadow /usr/local/bin/* 2>/dev/null 130 ``` 131 132 One subtle but useful detail: plain `restorecon` does **not always fully revert a suspicious label**. If the target type is in `customizable_types`, you may need `-F` to force a full reset. From an offensive perspective, this explains why an unusual `chcon` can sometimes survive a casual "we already ran restorecon" cleanup.<sup>[[8]](#references)</sup> 133 134 ```bash 135 grep -R . /etc/selinux/*/contexts/customizable_types 2>/dev/null | head 136 restorecon -n -v /path/of/interest 2>/dev/null 137 restorecon -F -v /path/of/interest 2>/dev/null 138 ``` 139 140 High-value commands to hunt in `sudo -l`, root wrappers, automation scripts, or file capabilities:<sup>[[1]](#references)[[4]](#references)[[5]](#references)[[12]](#references)[[13]](#references)[[14]](#references)</sup> 141 142 ```bash 143 which semanage restorecon chcon setfiles semodule audit2allow runcon newrole setsebool load_policy 2>/dev/null 144 getcap -r / 2>/dev/null | grep -E 'cap_mac_admin|cap_mac_override' 145 ``` 146 147 If either MAC capability shows up, cross-check the [Linux capabilities page](/hacktricks/linux-hardening/interesting-files-permissions/linux-capabilities) as well; the Linux capabilities documentation describes `cap_mac_admin` and `cap_mac_override` as Smack-specific, so do not assume that their names alone bypass SELinux.<sup>[[5]](#references)</sup> 148 149 Especially interesting:<sup>[[1]](#references)[[4]](#references)[[7]](#references)[[8]](#references)[[12]](#references)[[13]](#references)</sup> 150 151 - `semanage fcontext`: persistently changes what label a path should receive 152 - `restorecon` / `setfiles`: reapplies those changes at scale 153 - `semodule -i`: loads a custom policy module 154 - `semanage permissive -a <domain_t>`: makes one domain permissive without flipping the whole host 155 - `setsebool -P`: permanently changes policy booleans 156 - `load_policy`: reloads the active policy 157 158 These are often **helper primitives**, not standalone root exploits. Their value is that they let you:<sup>[[1]](#references)[[4]](#references)[[7]](#references)[[8]](#references)[[12]](#references)[[13]](#references)[[14]](#references)</sup> 159 160 - make a target domain permissive 161 - broaden access between your domain and a protected type 162 - relabel attacker-controlled files so a privileged service can read or execute them 163 - weaken a confined service enough that an existing local bug becomes exploitable 164 165 Example checks:<sup>[[1]](#references)[[7]](#references)[[8]](#references)</sup> 166 167 ```bash 168 # If sudo exposes semanage/restorecon, think in terms of policy abuse 169 sudo -l | grep -E 'semanage|restorecon|setfiles|semodule|runcon|newrole|setsebool|load_policy' 170 171 # Look for places where local file-context overrides may matter 172 semanage fcontext -C -l 2>/dev/null 173 restorecon -n -v /usr/local/bin /opt /srv /var/www 2>/dev/null 174 ``` 175 176 If you can load a policy module as root, you usually control the SELinux boundary:<sup>[[1]](#references)[[4]](#references)[[14]](#references)</sup> 177 178 ```bash 179 ausearch -m AVC,USER_AVC -ts recent 2>/dev/null | audit2allow -M localfix 180 sudo semodule -i localfix.pp 181 ``` 182 183 That is why `audit2allow`, `semodule`, and `semanage permissive` should be treated as sensitive admin surfaces during post-exploitation. They can silently convert a blocked chain into a working one without changing classic UNIX permissions.<sup>[[1]](#references)[[4]](#references)[[12]](#references)[[14]](#references)</sup> 184 185 ## Hidden Denials and Module Extraction 186 187 A very common offensive frustration is a chain that fails with a bland `EACCES` while the expected AVC denial never appears. `dontaudit` rules may be hiding the exact permission you need. If you can run `semodule` through `sudo` or another privileged wrapper, temporarily disabling `dontaudit` can turn a silent failure into a precise policy clue:<sup>[[4]](#references)[[15]](#references)</sup> 188 189 ```bash 190 # Rebuild policy without dontaudit rules, trigger the action again, then inspect AVCs 191 sudo semodule -DB 192 ausearch -m AVC,USER_AVC,SELINUX_ERR -ts recent 2>/dev/null | tail -n 50 193 sudo semodule -B 194 195 # Extract installed modules for offline review / diffing 196 semodule -lfull 2>/dev/null 197 semodule -E --cil <module_name> 2>/dev/null 198 ``` 199 200 This is also useful for reviewing what local admins already changed. A small custom module or one-domain permissive rule is often the reason a target service behaves much more loosely than the base policy would suggest.<sup>[[1]](#references)[[4]](#references)[[12]](#references)</sup> 201 202 ## Audit Clues 203 204 AVC denials are often offensive signal, not just defensive noise. They tell you:<sup>[[1]](#references)[[15]](#references)</sup> 205 206 - which target object/type you hit 207 - which permission was denied 208 - which domain you currently control 209 - whether a small policy change would make the chain work 210 211 ```bash 212 ausearch -m AVC,USER_AVC,SELINUX_ERR -ts recent 2>/dev/null 213 journalctl -t setroubleshoot --no-pager 2>/dev/null | tail -n 50 214 ``` 215 216 If a local exploit or persistence attempt keeps failing with `EACCES` or strange "permission denied" errors despite root-looking DAC permissions, SELinux is usually worth checking before discarding the vector.<sup>[[1]](#references)</sup> 217 218 ## SELinux Users 219 220 There are SELinux users in addition to regular Linux users. Each Linux user is mapped to an SELinux user as part of the policy, which lets the system impose different allowed roles and domains on different accounts.<sup>[[3]](#references)</sup> 221 222 Quick checks:<sup>[[3]](#references)</sup> 223 224 ```bash 225 id -Z 226 semanage login -l 2>/dev/null 227 semanage user -l 2>/dev/null 228 sudo -l 2>/dev/null 229 grep -R "ROLE=\|TYPE=" /etc/sudoers /etc/sudoers.d 2>/dev/null 230 ``` 231 232 On many mainstream systems, users are mapped to `unconfined_u`, which reduces the practical impact of user confinement. On hardened deployments, however, confined users can make `sudo`, `su`, `newrole`, and `runcon` much more interesting because **the escalation path may depend on entering a better SELinux role/type, not only on becoming UID 0**. Also remember that some confined users cannot invoke `sudo`/`su` at all unless policy explicitly allows the underlying setuid transition, so a host using `staff_u` + `sysadm_r` can turn a seemingly minor `sudo ROLE=` / `TYPE=` rule into the real privilege boundary.<sup>[[3]](#references)</sup> 233 234 ## SELinux in Containers 235 236 Container runtimes commonly launch workloads in a confined domain such as `container_t` and label container content as `container_file_t`. If a container process escapes but still runs with the container label, host writes may still fail because the label boundary stayed intact.<sup>[[1]](#references)[[17]](#references)</sup> 237 238 Quick example:<sup>[[16]](#references)[[18]](#references)</sup> 239 240 ```bash 241 $ podman run -d fedora sleep 100 242 d4194babf6b877c7100e79de92cd6717166f7302113018686cea650ea40bd7cb 243 $ podman top -l label 244 LABEL 245 system_u:system_r:container_t:s0:c647,c780 246 ``` 247 248 The `c647,c780` part is not decoration. In many container deployments, runtimes dynamically assign MCS categories so that two processes running as `container_t` are still separated from each other. If an escape lands you in a host namespace but keeps the original category set, category mismatches can still explain why some host paths remain unreadable or unwritable.<sup>[[17]](#references)</sup> 249 250 Modern container operations worth noting:<sup>[[16]](#references)[[17]](#references)</sup> 251 252 - `--security-opt label=disable` turns off SELinux label separation for the container 253 - bind mounts with `:z` / `:Z` trigger relabeling of the host path for shared/private container use 254 - broad relabeling of host content can become a security issue on its own 255 256 This page keeps the container content short to avoid duplication. For the container-specific abuse cases and runtime examples, check: 257 258 [Selinux](/hacktricks/linux-hardening/containers-namespaces/container-security/protections/selinux) 259 260 ## References 261 262 - [1] [Red Hat docs: Using SELinux](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html-single/using_selinux/index) 263 - [2] [SETools: Policy analysis tools for SELinux](https://github.com/SELinuxProject/setools) 264 - [3] [Managing confined and unconfined users - RHEL 9 docs](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/using_selinux/managing-confined-and-unconfined-users_using-selinux) 265 - [4] [semodule(8) - Linux manual page](https://man7.org/linux/man-pages/man8/semodule.8.html) 266 - [5] [capabilities(7) - Linux manual page](https://man7.org/linux/man-pages/man7/capabilities.7.html) 267 - [6] [chcon(1) - Linux manual page](https://man7.org/linux/man-pages/man1/chcon.1.html) 268 - [7] [semanage-fcontext(8) - Linux manual page](https://man7.org/linux/man-pages/man8/semanage-fcontext.8.html) 269 - [8] [restorecon(8) - Linux manual page](https://man7.org/linux/man-pages/man8/restorecon.8.html) 270 - [9] [sepolicy-transition(8) - Linux manual page](https://man7.org/linux/man-pages/man8/sepolicy-transition.8.html) 271 - [10] [runcon(1) - Linux manual page](https://man7.org/linux/man-pages/man1/runcon.1.html) 272 - [11] [newrole(1) - Linux manual page](https://man7.org/linux/man-pages/man1/newrole.1.html) 273 - [12] [semanage-permissive(8) - Linux manual page](https://man7.org/linux/man-pages/man8/semanage-permissive.8.html) 274 - [13] [setsebool(8) - Linux manual page](https://man7.org/linux/man-pages/man8/setsebool.8.html) 275 - [14] [audit2allow(1) - Linux manual page](https://man7.org/linux/man-pages/man1/audit2allow.1.html) 276 - [15] [ausearch(8) - Linux manual page](https://man7.org/linux/man-pages/man8/ausearch.8.html) 277 - [16] [Podman run documentation](https://docs.podman.io/en/latest/markdown/podman-run.1.html) 278 - [17] [Why you should be using Multi-Category Security for your Linux containers](https://www.redhat.com/en/blog/why-you-should-be-using-multi-category-security-your-linux-containers) 279 - [18] [Podman top documentation](https://docs.podman.io/en/latest/markdown/podman-top.1.html) 280 - [19] [selinux(8) - Linux manual page](https://man7.org/linux/man-pages/man8/selinux.8.html)